Yxxe Posted March 2, 2011 Author Share Posted March 2, 2011 **Lightning's Pet System**Hello, and welcome to my tutorial on creating a Basic Pet System. When this tutorial is over, you should have created a solid Pet System base to work on further, which also includes a few key features for any pet system. Before you get started, please make sure you have the following:* Visual Basic 6 Professional/Enterprise* Client/Server Source Files* Basic understanding of the use of the VB6 IDE and Language* A brain**Compatable with: Eclipse Origins 2.0.0****Translations:**Below are translations of this tutorial.[Lightning's Pet System in German by EisKeks](http://www.touchofdeathforums.com/smf/index.php/topic,71065.new.html#new) (OUTDATED)**Optional Mods:**Below are modifications other users have created for this pet system. It is recommended for most mods that you follow this tutorial first, then go and mod afterwards. I have also given the tutorial a star rating (out of five) and a brief review. Please do not PM me for support of these mods, they are not created by me, and questions should be asked to the respective creators.>! [Multiple Pets (Author - RyokuHasu)](http://www.touchofdeathforums.com/smf/index.php/topic,69925.0.html)This tutorial will help if you have a specific class that requires summoning different pets. It's a fairly necessary addition, simple to add, but fairly limited in its use. :bstar: :bstar: :bstar: :rstar: :rstar: (3/5)>! [Modifications 2.5 Nova (Author - Richy)](http://www.touchofdeathforums.com/smf/index.php/topic,69078.0.html)Not a tutorial, but a very good mod if Visual Basic 6 is not available to you. Richy has taken my Base Pet System and modded it to his and others' needs. It also comes with other various features, which I will not go in to. :bstar: :bstar: :bstar: :bstar: :rstar: (4/5)>! Multiple Items and Percentile Drops Addition (Author - Xlithan)>! >! For anybody who has added Multiple Item Drops and Percentiles, Find:>! ```' Drop the goods if they get it```in NPCAttackNPC sub, and change to this:>! ``` ' Drop the goods if they get it For n = 1 To MAX_NPC_DROPS If Npc(Victim).DropItem(n) = 0 Then Exit For If Rnd <= Npc(Victim).DropChance(n) Then Call SpawnItem(Npc(Victim).DropItem(n), Npc(Victim).DropItemValue(n), MapNum, MapNpc(MapNum).Npc(Victim).x, MapNpc(MapNum).Npc(Victim).y) End If Next``` **Outcome**At the end of this tutorial, you should be able to use a pet, maybe something like this:![](http://i801.photobucket.com/albums/yy294/Adrammelech_2009/SummonedPet.png)![](http://i801.photobucket.com/albums/yy294/Adrammelech_2009/PetCombat.png)**Client Side**First, we will work on the client side code. This is mainly sending commands to the server to control your pet. These commands include:* Calling your Pet* Attack a Target* Simple NPC Following* Letting your pet wander around the map* Disbanding your pet.First of all, you will need to work your way to modGameLogic.Scroll to the bottom of the module and paste these five procedures:```Sub SpawnPet(ByVal Index As Long) Dim Buffer As clsBuffer Set Buffer = New clsBuffer Buffer.WriteLong CSpawnPet SendData Buffer.ToArray() Set Buffer = NothingEnd SubSub PetFollow(ByVal Index As Long) Dim Buffer As clsBuffer Set Buffer = New clsBuffer Buffer.WriteLong CPetFollowOwner SendData Buffer.ToArray() Set Buffer = NothingEnd SubSub PetAttack(ByVal Index As Long) Dim Buffer As clsBuffer Set Buffer = New clsBuffer Buffer.WriteLong CPetAttackTarget SendData Buffer.ToArray() Set Buffer = NothingEnd SubSub PetWander(ByVal Index As Long) Dim Buffer As clsBuffer Set Buffer = New clsBuffer Buffer.WriteLong CPetWander SendData Buffer.ToArray() Set Buffer = NothingEnd SubSub PetDisband(ByVal Index As Long) Dim Buffer As clsBuffer Set Buffer = New clsBuffer Buffer.WriteLong CPetDisband SendData Buffer.ToArray() Set Buffer = NothingEnd Sub```These procedures are the commands for controlling your pet. Note the names of the procedures are named appropriately for each command.Next, you will need to open up modEnumerations.Scroll to the bottom of the client-side enumerations (The ones beginning with a "C"), and paste this above CMSG_COUNT:```CSpawnPetCPetFollowOwnerCPetAttackTargetCPetWanderCPetDisband```Next, go to modHandleData and replace "Sub HandleSpawnNpc" with this:```Private Sub HandleSpawnNpc(ByVal Index As Long, ByRef Data() As Byte, ByVal StartAddr As Long, ByVal ExtraVar As Long)Dim n As LongDim Buffer As clsBuffer ' If debug mode, handle error then exit out If Options.Debug = 1 Then On Error GoTo errorhandler Set Buffer = New clsBuffer Buffer.WriteBytes Data() n = Buffer.ReadLong With MapNpc(n) .Num = Buffer.ReadLong .x = Buffer.ReadLong .y = Buffer.ReadLong .Dir = Buffer.ReadLong .IsPet = Buffer.ReadByte .PetData.Name = Buffer.ReadString .PetData.Owner = Buffer.ReadLong ' Client use only .XOffset = 0 .YOffset = 0 .Moving = 0 End With ' Error handler Exit Suberrorhandler: HandleError "HandleSpawnNpc", "modHandleData", Err.Number, Err.Description, Err.Source, Err.HelpContext Err.Clear Exit SubEnd Sub```After you have done this, you will need to create five buttons or labels to trigger each of the commands.Place them in the options menu, or in any other menu you wish. After you have created them, they should look something like this:![](http://i801.photobucket.com/albums/yy294/Adrammelech_2009/Commands.png)You will then need to call each of the procedures earlier for each of their respective functions. The parameter you will need to pass into them will be "MyIndex" for each.Next, go to modHandleData and add this procedure at the bottom:```Private Sub HandleNPCCache(ByVal Index As Long, ByRef Data() As Byte, ByVal StartAddr As Long, ByVal ExtraVar As Long) Dim Buffer As clsBuffer Dim PIndex As Long Dim MapNum As Long Dim NPCNum As Long Dim i As Long Set Buffer = New clsBuffer Buffer.WriteBytes Data() MapNum = Buffer.ReadLong NPCNum = Buffer.ReadLong Map.Npc(NPCNum) = Buffer.ReadLong MapNpc(NPCNum).Num = Buffer.ReadLong SaveMap (MapNum) Set Buffer = NothingEnd Sub```Go to Sub InitMessages in modHandleData still, and add this just above "Exit Sub":```HandleDataSub(SNPCCache) = GetAddress(AddressOf HandleNPCCache)```Finally, you will need to add the code below to the bottom of "MapNpcRec" in modTypes:``` 'Pet Data IsPet As Byte PetData As PetRec```Below "Dir As Byte" in PlayerRec, add this:```Pet As PetRec```and add this custom type above PlayerRec:```Public Type PetRec SpriteNum As Byte Name As String * 50 Owner As LongEnd Type```**Server Side**We can now move on to the more complicated server-side code. The server-side code mainly handles the spawning and de-spawning of the pet, and handling combat.Firstly, go to "Sub JoinGame" and add this piece of code below "Call PlayerWarp(…)":```'View Current Pets on Map If PetMapCache(Player(Index).Map).UpperBound > 0 Then For j = 1 To PetMapCache(Player(Index).Map).UpperBound Call NPCCache_Create(Index, Player(Index).Map, PetMapCache(Player(Index).Map).Pet(j)) Next End If```Place this at the top of "Sub JoinGame"```Dim i As Long, j As Long```This sends the pre-cached Pets to the new player to "synchronise" it with the server.Next, add this wherever you see fit:```Public Type PetCache Pet(1 To MAX_MAP_NPCS) As Long UpperBound As LongEnd TypePublic PetMapCache(1 To MAX_MAPS) As PetCache```This caches NPCs for new players logging on.Next, you will need to open up modEnumerations.Scroll to the bottom of the client-side enumerations (The ones beginning with a "C"), and paste this above CMSG_COUNT:```CSpawnPetCPetFollowOwnerCPetAttackTargetCPetWanderCPetDisband```Next, move to modHandleData and look for "Public Sub InitMessages()". Scroll to the bottom of the procedure and add:``` 'Pet System HandleDataSub(CSpawnPet) = GetAddress(AddressOf HandleSpawnPet) HandleDataSub(CPetFollowOwner) = GetAddress(AddressOf HandlePetFollowOwner) HandleDataSub(CPetAttackTarget) = GetAddress(AddressOf HandlePetAttackTarget) HandleDataSub(CPetWander) = GetAddress(AddressOf HandlePetWander) HandleDataSub(CPetDisband) = GetAddress(AddressOf HandlePetDisband)```These five lines will call their respective procedures when the correct packet is recieved. Once you have pasted these lines in, scroll to the bottom of modHandleData and add these five procedures:```Public Sub HandleSpawnPet(ByVal Index As Long, ByRef Data() As Byte, ByVal StartAddr As Long, ByVal ExtraVar As Long) SpawnPet Index, GetPlayerMap(Index)End SubPublic Sub HandlePetFollowOwner(ByVal Index As Long, ByRef Data() As Byte, ByVal StartAddr As Long, ByVal ExtraVar As Long) Dim Buffer As clsBuffer PetFollowOwner IndexEnd SubPublic Sub HandlePetAttackTarget(ByVal Index As Long, ByRef Data() As Byte, ByVal StartAddr As Long, ByVal ExtraVar As Long) Dim Buffer As clsBuffer If TempPlayer(Index).TempPetSlot < 1 Then Exit Sub MapNpc(GetPlayerMap(Index)).Npc(TempPlayer(Index).TempPetSlot).targetType = TempPlayer(Index).targetType MapNpc(GetPlayerMap(Index)).Npc(TempPlayer(Index).TempPetSlot).target = TempPlayer(Index).targetEnd SubPublic Sub HandlePetWander(ByVal Index As Long, ByRef Data() As Byte, ByVal StartAddr As Long, ByVal ExtraVar As Long) Dim Buffer As clsBuffer PetWander IndexEnd SubPublic Sub HandlePetDisband(ByVal Index As Long, ByRef Data() As Byte, ByVal StartAddr As Long, ByVal ExtraVar As Long) Dim Buffer As clsBuffer PetDisband Index, GetPlayerMap(Index) SendMap Index, GetPlayerMap(Index) PlayerWarp Index, GetPlayerMap(Index), GetPlayerX(Index), GetPlayerY(Index)End Sub```After that, move to modGameLogic and add these procedures to the bottom of it:```'makes the pet follow its ownerSub PetFollowOwner(ByVal Index As Long) If TempPlayer(Index).TempPetSlot < 1 Then Exit Sub MapNpc(GetPlayerMap(Index)).Npc(TempPlayer(Index).TempPetSlot).targetType = 1 MapNpc(GetPlayerMap(Index)).Npc(TempPlayer(Index).TempPetSlot).target = IndexEnd Sub'makes the pet wander around the mapSub PetWander(ByVal Index As Long) If TempPlayer(Index).TempPetSlot < 1 Then Exit Sub MapNpc(GetPlayerMap(Index)).Npc(TempPlayer(Index).TempPetSlot).targetType = TARGET_TYPE_NONE MapNpc(GetPlayerMap(Index)).Npc(TempPlayer(Index).TempPetSlot).target = 0End Sub'Clear the npc from the mapSub PetDisband(ByVal index As Long, ByVal MapNum As Long) Dim i As Long Dim j As Long If TempPlayer(index).TempPetSlot < 1 Then Exit Sub 'Cache the Pets for players logging on [Remove Number from array] 'THIS IS KINDA SLOW (EVEN WITHOUT TESTING, LOL), MAY HAVE TO CONVERT TO LINKED LIST FOR SPEED For i = 1 To PetMapCache(MapNum).UpperBound If PetMapCache(MapNum).Pet(i) = TempPlayer(index).TempPetSlot Then If PetMapCache(MapNum).UpperBound > 1 Then For j = PetMapCache(MapNum).UpperBound To i Step -1 PetMapCache(MapNum).Pet(j - 1) = PetMapCache(MapNum).Pet(j) Next Else PetMapCache(MapNum).Pet(1) = 0 End If PetMapCache(MapNum).UpperBound = PetMapCache(MapNum).UpperBound - 1 Exit For End If Next Call ClearSingleMapNpc(TempPlayer(index).TempPetSlot, MapNum) Map(GetPlayerMap(index)).Npc(TempPlayer(index).TempPetSlot) = 0 TempPlayer(index).TempPetSlot = 0 're-warp the players on the map For i = 1 To Player_HighIndex If IsPlaying(i) Then If GetPlayerMap(i) = GetPlayerMap(index) Then Call PlayerWarp(i, GetPlayerMap(i), GetPlayerX(i), GetPlayerY(i)) SendPlayerData index End If End If NextEnd SubSub SpawnPet(ByVal Index As Long, ByVal MapNum As Long) Dim PlayerMap As Long Dim I As Integer Dim PetSlot As Byte 'Prevent multiple pets for the same owner If TempPlayer(Index).TempPetSlot > 0 Then Exit Sub PlayerMap = GetPlayerMap(Index) PetSlot = 0 For i = 1 To MAX_MAP_NPCS 'If Map(PlayerMap).Npc(i) = 0 Then If MapNpc(PlayerMap).Npc(i).SpawnWait = 0 And MapNpc(PlayerMap).Npc(i).Num = 0 Then PetSlot = i Exit For End If Next If PetSlot = 0 Then Call PlayerMsg(Index, "The map is too crowded for you to call on your pet!", Red) Exit Sub End If 'create the pet for the map Map(PlayerMap).Npc(PetSlot) = # MapNpc(PlayerMap).Npc(PetSlot).Num = # 'set its Pet Data MapNpc(PlayerMap).Npc(PetSlot).IsPet = YES MapNpc(PlayerMap).Npc(PetSlot).PetData.Name = GetPlayerName(Index) & "'s " & Npc(#).Name MapNpc(PlayerMap).Npc(PetSlot).PetData.Owner = Index 'If Pet doesn't exist with player, link it to the player If Player(Index).Pet.SpriteNum <> # Then Player(Index).Pet.SpriteNum = # Player(Index).Pet.Name = GetPlayerName(Index) & "'s " & Npc(#).Name End If TempPlayer(Index).TempPetSlot = PetSlot 'cache the map for sending Call MapCache_Create(PlayerMap) 'Cache the Pets for players logging on [Add new Number to array] PetMapCache(PlayerMap).UpperBound = PetMapCache(PlayerMap).UpperBound + 1 PetMapCache(PlayerMap).Pet(PetMapCache(PlayerMap).UpperBound) = PetSlot If PetMapCache(Player(Index).Map).UpperBound > 0 Then For i = 1 To PetMapCache(Player(Index).Map).UpperBound Call NPCCache_Create(Index, Player(Index).Map, PetMapCache(Player(Index).Map).Pet(i)) Next End If Select Case GetPlayerDir(Index) Case DIR_UP Call SpawnNpc(PetSlot, PlayerMap, GetPlayerX(Index), GetPlayerY(Index) - 1) Case DIR_DOWN Call SpawnNpc(PetSlot, PlayerMap, GetPlayerX(Index), GetPlayerY(Index) + 1) Case DIR_LEFT Call SpawnNpc(PetSlot, PlayerMap, GetPlayerX(Index) + 1, GetPlayerY(Index)) Case DIR_RIGHT Call SpawnNpc(PetSlot, PlayerMap, GetPlayerX(Index), GetPlayerY(Index) - 1) End Select 're-warp the players on the map For I = 1 To Player_HighIndex If IsPlaying(I) Then If GetPlayerMap(I) = GetPlayerMap(Index) Then Call PlayerWarp(i, PlayerMap, GetPlayerX(i), GetPlayerY(i)) End If End If NextEnd Sub```The names of the procedures clearly explain their function. The code should also be easy to follow. Replace the hashes with your desired NPC number.Staying in modGameLogic, you will need to navigate your way to Sub SpawnNpc. Delete that procedure, and replace it with this:```Public Sub SpawnNpc(ByVal mapNpcNum As Long, ByVal MapNum As Long, Optional ByVal SetX As Long, Optional ByVal SetY As Long) Dim Buffer As clsBuffer Dim npcNum As Long Dim I As Long Dim x As Long Dim y As Long Dim Spawned As Boolean ' Check for subscript out of range If mapNpcNum <= 0 Or mapNpcNum > MAX_MAP_NPCS Or MapNum <= 0 Or MapNum > MAX_MAPS Then Exit Sub npcNum = Map(MapNum).Npc(mapNpcNum) If npcNum > 0 Then MapNpc(MapNum).Npc(mapNpcNum).Num = npcNum MapNpc(MapNum).Npc(mapNpcNum).target = 0 MapNpc(MapNum).Npc(mapNpcNum).targetType = 0 ' clear MapNpc(MapNum).Npc(mapNpcNum).Vital(Vitals.HP) = GetNpcMaxVital(npcNum, Vitals.HP) MapNpc(MapNum).Npc(mapNpcNum).Vital(Vitals.MP) = GetNpcMaxVital(npcNum, Vitals.MP) MapNpc(MapNum).Npc(mapNpcNum).Dir = Int(Rnd * 4) 'Check if theres a spawn tile for the specific npc For x = 0 To Map(MapNum).MaxX For y = 0 To Map(MapNum).MaxY If Map(MapNum).Tile(x, y).Type = TILE_TYPE_NPCSPAWN Then If Map(MapNum).Tile(x, y).Data1 = mapNpcNum Then MapNpc(MapNum).Npc(mapNpcNum).x = x MapNpc(MapNum).Npc(mapNpcNum).y = y MapNpc(MapNum).Npc(mapNpcNum).Dir = Map(MapNum).Tile(x, y).Data2 Spawned = True Exit For End If End If Next y Next x If Not Spawned Then ' Well try 100 times to randomly place the sprite For I = 1 To 100 If SetX = 0 And SetY = 0 Then x = Random(0, Map(MapNum).MaxX) y = Random(0, Map(MapNum).MaxY) Else x = SetX y = SetY End If If x > Map(MapNum).MaxX Then x = Map(MapNum).MaxX If y > Map(MapNum).MaxY Then y = Map(MapNum).MaxY ' Check if the tile is walkable If NpcTileIsOpen(MapNum, x, y) Then MapNpc(MapNum).Npc(mapNpcNum).x = x MapNpc(MapNum).Npc(mapNpcNum).y = y Spawned = True Exit For End If Next End If ' Didn't spawn, so now we'll just try to find a free tile If Not Spawned Then For x = 0 To Map(MapNum).MaxX For y = 0 To Map(MapNum).MaxY If NpcTileIsOpen(MapNum, x, y) Then MapNpc(MapNum).Npc(mapNpcNum).x = x MapNpc(MapNum).Npc(mapNpcNum).y = y Spawned = True End If Next Next End If ' If we suceeded in spawning then send it to everyone If Spawned Then Set Buffer = New clsBuffer Buffer.WriteLong SSpawnNpc Buffer.WriteLong mapNpcNum Buffer.WriteLong MapNpc(MapNum).Npc(mapNpcNum).Num Buffer.WriteLong MapNpc(MapNum).Npc(mapNpcNum).x Buffer.WriteLong MapNpc(MapNum).Npc(mapNpcNum).y Buffer.WriteLong MapNpc(MapNum).Npc(mapNpcNum).Dir Buffer.WriteByte MapNpc(MapNum).Npc(mapNpcNum).IsPet Buffer.WriteString MapNpc(MapNum).Npc(mapNpcNum).PetData.Name Buffer.WriteLong MapNpc(MapNum).Npc(mapNpcNum).PetData.Owner SendDataToMap MapNum, Buffer.ToArray() Set Buffer = Nothing End If SendMapNpcVitals MapNum, mapNpcNum End IfEnd Sub```Another parameter to this procedure could easily be added for various NPC numbers, triggered by using an item.Now move to modCombat. Add these procedures to the bottom of the module. They are TryNpcAttackNpc and CanNpcAttackNpc. These are basically ripped from an old version of EO with a few modifications by me, so credits go to Robin for them. They are used in this tutorial for Pet vs. Other NPC combat.```Function CanNpcAttackNpc(ByVal MapNum As Long, ByVal Attacker As Long, ByVal Victim As Long) As Boolean Dim aNpcNum As Long Dim vNpcNum As Long Dim VictimX As Long Dim VictimY As Long Dim AttackerX As Long Dim AttackerY As Long CanNpcAttackNpc = False ' Check for subscript out of range If Attacker <= 0 Or Attacker > MAX_MAP_NPCS Then Exit Function End If If Victim <= 0 Or Victim > MAX_MAP_NPCS Then Exit Function End If ' Check for subscript out of range If MapNpc(MapNum).Npc(Attacker).Num <= 0 Then Exit Function End If ' Check for subscript out of range If MapNpc(MapNum).Npc(Victim).Num <= 0 Then Exit Function End If aNpcNum = MapNpc(MapNum).Npc(Attacker).Num vNpcNum = MapNpc(MapNum).Npc(Victim).Num If aNpcNum <= 0 Then Exit Function If vNpcNum <= 0 Then Exit Function ' Make sure the npcs arent already dead If MapNpc(MapNum).Npc(Attacker).Vital(Vitals.HP) <= 0 Then Exit Function End If ' Make sure the npc isn't already dead If MapNpc(MapNum).Npc(Victim).Vital(Vitals.HP) <= 0 Then Exit Function End If ' Make sure npcs dont attack more then once a second If GetTickCount < MapNpc(MapNum).Npc(Attacker).AttackTimer + 1000 Then Exit Function End If MapNpc(MapNum).Npc(Attacker).AttackTimer = GetTickCount AttackerX = MapNpc(MapNum).Npc(Attacker).x AttackerY = MapNpc(MapNum).Npc(Attacker).y VictimX = MapNpc(MapNum).Npc(Victim).x VictimY = MapNpc(MapNum).Npc(Victim).y ' Check if at same coordinates If (VictimY + 1 = AttackerY) And (VictimX = AttackerX) Then CanNpcAttackNpc = True Else If (VictimY - 1 = AttackerY) And (VictimX = AttackerX) Then CanNpcAttackNpc = True Else If (VictimY = AttackerY) And (VictimX + 1 = AttackerX) Then CanNpcAttackNpc = True Else If (VictimY = AttackerY) And (VictimX - 1 = AttackerX) Then CanNpcAttackNpc = True End If End If End If End IfEnd FunctionSub NpcAttackNpc(ByVal MapNum As Long, ByVal Attacker As Long, ByVal Victim As Long, ByVal Damage As Long) Dim i As Long Dim Buffer As clsBuffer Dim aNpcNum As Long Dim vNpcNum As Long Dim n As Long Dim PetOwner As Long If Attacker <= 0 Or Attacker > MAX_MAP_NPCS Then Exit Sub If Victim <= 0 Or Victim > MAX_MAP_NPCS Then Exit Sub If Damage <= 0 Then Exit Sub aNpcNum = MapNpc(MapNum).Npc(Attacker).Num vNpcNum = MapNpc(MapNum).Npc(Victim).Num If aNpcNum <= 0 Then Exit Sub If vNpcNum <= 0 Then Exit Sub 'set the victim's target to the pet attacking it MapNpc(MapNum).Npc(Victim).targetType = 2 'Npc MapNpc(MapNum).Npc(Victim).target = Attacker ' Send this packet so they can see the person attacking Set Buffer = New clsBuffer Buffer.WriteLong SNpcAttack Buffer.WriteLong Attacker SendDataToMap MapNum, Buffer.ToArray() Set Buffer = Nothing If Damage >= MapNpc(MapNum).Npc(Victim).Vital(Vitals.HP) Then SendActionMsg MapNum, "-" & Damage, BrightRed, 1, (MapNpc(MapNum).Npc(Victim).x * 32), (MapNpc(MapNum).Npc(Victim).y * 32) SendBlood MapNum, MapNpc(MapNum).Npc(Victim).x, MapNpc(MapNum).Npc(Victim).y ' npc is dead. 'Call GlobalMsg(CheckGrammar(Trim$(Npc(vNpcNum).Name), 1) & " has been killed by " & CheckGrammar(Trim$(Npc(aNpcNum).Name)) & "!", BrightRed) ' Set NPC target to 0 MapNpc(MapNum).Npc(Attacker).target = 0 MapNpc(MapNum).Npc(Attacker).targetType = 0 'reset the targetter for the player If MapNpc(MapNum).Npc(Attacker).IsPet = YES Then TempPlayer(MapNpc(MapNum).Npc(Attacker).PetData.Owner).target = 0 TempPlayer(MapNpc(MapNum).Npc(Attacker).PetData.Owner).targetType = TARGET_TYPE_NONE PetOwner = MapNpc(MapNum).Npc(Attacker).PetData.Owner SendTarget PetOwner 'Give the player the pet owner some experience from the kill Call SetPlayerExp(PetOwner, GetPlayerExp(PetOwner) + Npc(MapNpc(MapNum).Npc(Victim).Num).Exp) CheckPlayerLevelUp PetOwner SendActionMsg MapNum, "+" & Npc(MapNpc(MapNum).Npc(Victim).Num).Exp & "Exp", White, 1, GetPlayerX(PetOwner) * 32, GetPlayerY(PetOwner) * 32 SendEXP PetOwner ElseIf MapNpc(MapNum).Npc(Victim).IsPet = YES Then 'Get the pet owners' index PetOwner = MapNpc(MapNum).Npc(Victim).PetData.Owner 'Set the NPC's target on the owner now MapNpc(MapNum).Npc(Attacker).targetType = 1 'player MapNpc(MapNum).Npc(Attacker).target = PetOwner 'Disband the pet PetDisband PetOwner, GetPlayerMap(PetOwner) End If ' Drop the goods if they get it 'For n = 1 To MAX_NPC_DROPS If Npc(vNpcNum).DropItem <> 0 Then If Rnd <= Npc(vNpcNum).DropChance Then Call SpawnItem(Npc(vNpcNum).DropItem, Npc(vNpcNum).DropItemValue, MapNum, MapNpc(MapNum).Npc(Victim).x, MapNpc(MapNum).Npc(Victim).y) End If End If 'Next ' Reset victim's stuff so it dies in loop MapNpc(MapNum).Npc(Victim).Num = 0 MapNpc(MapNum).Npc(Victim).SpawnWait = GetTickCount MapNpc(MapNum).Npc(Victim).Vital(Vitals.HP) = 0 ' send npc death packet to map Set Buffer = New clsBuffer Buffer.WriteLong SNpcDead Buffer.WriteLong Victim SendDataToMap MapNum, Buffer.ToArray() Set Buffer = Nothing If PetOwner > 0 Then PetFollowOwner PetOwner End If Else ' npc not dead, just do the damage MapNpc(MapNum).Npc(Victim).Vital(Vitals.HP) = MapNpc(MapNum).Npc(Victim).Vital(Vitals.HP) - Damage ' Say damage SendActionMsg MapNum, "-" & Damage, BrightRed, 1, (MapNpc(MapNum).Npc(Victim).x * 32), (MapNpc(MapNum).Npc(Victim).y * 32) SendBlood MapNum, MapNpc(MapNum).Npc(Victim).x, MapNpc(MapNum).Npc(Victim).y End If 'Send both Npc's Vitals to the client SendMapNpcVitals MapNum, Attacker SendMapNpcVitals MapNum, VictimEnd Sub```Now search for "Sub PlayerAttackNPC". Add this below where the Map NPC's HP is set to zero:```'Checks if NPC was a pet If MapNpc(MapNum).Npc(mapNpcNum).IsPet = YES Then Call PetDisband(MapNpc(MapNum).Npc(mapNpcNum).PetData.Owner, MapNum) 'The pet was killed End If```Staying in modCombat, find the Function: "CanNpcAttackPlayer". Underneath the main checks: Add this:```'check if the NPC attacking us is actually our pet.'We don't want a rebellion on our hands now do we?If MapNpc(MapNum).Npc(mapNpcNum).PetData.Owner = Index Then Exit Function```This stops the pet attacking it's owner. >_<Next, ctrl+F "Sub CloseSocket". Replace the current procedure with this:```Sub CloseSocket(ByVal Index As Long)Dim I As Integer If Index > 0 Then If TempPlayer(Index).TempPetSlot > 0 Then Call PetDisband(Index, GetPlayerMap(Index)) For I = 1 To Player_HighIndex If GetPlayerMap(I) = GetPlayerMap(Index) Then SendMap I, GetPlayerMap(Index) End If Next End If Call LeftGame(Index) Call TextAdd("Connection from " & GetPlayerIP(Index) & " has been terminated.") frmServer.Socket(Index).Close Call UpdateCaption Call ClearPlayer(Index) End IfEnd Sub```The modification here disbands a pet if its owner logs out.After this, Ctrl+F "This is used for npcs to attack targets". This will move you into modServerLoop>UpdateMapLogic.In that section, up to "This is used for regenerating NPC's HP, replace that code with this:```If Map(MapNum).Npc(x) > 0 And MapNpc(MapNum).Npc(x).Num > 0 Then target = MapNpc(MapNum).Npc(x).target targetType = MapNpc(MapNum).Npc(x).targetType ' Check if the npc can attack the targeted player player If target > 0 Then If targetType = 1 Then ' player ' Is the target playing and on the same map? If IsPlaying(target) And GetPlayerMap(target) = MapNum Then TryNpcAttackPlayer x, target Else ' Player left map or game, set target to 0 MapNpc(MapNum).Npc(x).target = 0 MapNpc(MapNum).Npc(x).targetType = 0 ' clear End If ElseIf targetType = 2 Then ' lol no npc combat :( DATS WAT YOU THINK If CanNpcAttackNpc(MapNum, x, MapNpc(MapNum).Npc(x).target) = True Then Call NpcAttackNpc(MapNum, x, MapNpc(MapNum).Npc(x).target, Npc(Map(MapNum).Npc(x)).Damage) End If Else End If End If End If```The modification I have done here basically handles the NPC vs. NPC combat for your pet, mentioned earlier.Afterwards, go to modDatabase and add this:```Sub ClearSingleMapNpc(ByVal index As Long, ByVal MapNum As Long) Call ZeroMemory(ByVal VarPtr(MapNpc(MapNum).Npc(index)), LenB(MapNpc(MapNum).Npc(index))) Map(MapNum).Npc(index) = 0 MapNpc(MapNum).Npc(index).Num = 0 MapCache_Create (MapNum)End Subb```This helps for clearing a single npc from a map; whether on death or dismissal.Next, move to modPlayer and find the procedure named "PlayerWarp". Replace that procedure with this modded version:```Sub PlayerWarp(ByVal Index As Long, ByVal MapNum As Long, ByVal x As Long, ByVal y As Long) Dim shopNum As Long Dim OldMap As Long Dim I As Long Dim Buffer As clsBuffer ' Check for subscript out of range If IsPlaying(Index) = False Or MapNum <= 0 Or MapNum > MAX_MAPS Then Exit Sub End If ' Check if you are out of bounds If x > Map(MapNum).MaxX Then x = Map(MapNum).MaxX If y > Map(MapNum).MaxY Then y = Map(MapNum).MaxY If x < 0 Then x = 0 If y < 0 Then y = 0 ' if same map then just send their co-ordinates If MapNum = GetPlayerMap(Index) Then SendPlayerXYToMap Index End If ' clear target TempPlayer(Index).target = 0 TempPlayer(Index).targetType = TARGET_TYPE_NONE SendTarget Index ' Save old map to send erase player data to OldMap = GetPlayerMap(Index) If OldMap <> MapNum Then Call SendLeaveMap(Index, OldMap) End If Call SetPlayerMap(Index, MapNum) Call SetPlayerX(Index, x) Call SetPlayerY(Index, y) 'If 'refreshing' map If (OldMap <> MapNum) And TempPlayer(Index).TempPetSlot > 0 Then 'switch maps PetDisband Index, OldMap SpawnPet Index, MapNum PetFollowOwner Index If PetMapCache(OldMap).UpperBound > 0 Then For i = 1 To PetMapCache(OldMap).UpperBound If PetMapCache(OldMap).Pet(i) = TempPlayer(Index).TempPetSlot Then PetMapCache(OldMap).Pet(i) = 0 End If Next Else PetMapCache(OldMap).Pet(1) = 0 End If End If 'View Current Pets on Map If PetMapCache(Player(Index).Map).UpperBound > 0 Then For i = 1 To PetMapCache(Player(Index).Map).UpperBound Call NPCCache_Create(Index, Player(Index).Map, PetMapCache(Player(Index).Map).Pet(i)) Next End If ' send player's equipment to new map SendMapEquipment Index ' send equipment of all people on new map If GetTotalMapPlayers(MapNum) > 0 Then For I = 1 To Player_HighIndex If IsPlaying(I) Then If GetPlayerMap(I) = MapNum Then SendMapEquipmentTo I, Index End If End If Next End If ' Now we check if there were any players left on the map the player just left, and if not stop processing npcs If GetTotalMapPlayers(OldMap) = 0 Then PlayersOnMap(OldMap) = NO ' Regenerate all NPCs' health For I = 1 To MAX_MAP_NPCS If MapNpc(OldMap).Npc(I).Num > 0 Then MapNpc(OldMap).Npc(I).Vital(Vitals.HP) = GetNpcMaxVital(MapNpc(OldMap).Npc(I).Num, Vitals.HP) End If Next End If ' Sets it so we know to process npcs on the map PlayersOnMap(MapNum) = YES TempPlayer(Index).GettingMap = YES Set Buffer = New clsBuffer Buffer.WriteLong SCheckForMap Buffer.WriteLong MapNum Buffer.WriteLong Map(MapNum).Revision SendDataTo Index, Buffer.ToArray() Set Buffer = NothingEnd Sub```The modification here merely de-spawns the pet from the old map and spawns it onto the new one.Next, go to modServerTCP, and add these two subs at the bottom of it:```Sub NPCCache_Create_SendToAll(ByVal MapNum As Long, ByVal NPCNum As Long) Dim i As Long Dim Buffer As clsBuffer Set Buffer = New clsBuffer Buffer.WriteLong SNPCCache Buffer.WriteLong MapNum Buffer.WriteLong NPCNum Buffer.WriteLong Map(MapNum).Npc(NPCNum) Buffer.WriteLong MapNpc(MapNum).Npc(NPCNum).Num For i = 1 To Player_HighIndex If IsPlaying(i) Then If Player(i).Map = MapNum Then SendDataTo i, Buffer.ToArray() End If End If NextEnd SubSub NPCCache_Create(ByVal Index As Long, ByVal MapNum As Long, ByVal NPCNum As Long) Dim i As Long Dim Buffer As clsBuffer Set Buffer = New clsBuffer Buffer.WriteLong SNPCCache Buffer.WriteLong MapNum Buffer.WriteLong NPCNum Buffer.WriteLong Map(MapNum).Npc(NPCNum) Buffer.WriteLong MapNpc(MapNum).Npc(NPCNum).Num SendDataTo Index, Buffer.ToArray()End Sub```You will also need to add "SNPCCache" to modEnumerations.Finally, move to modTypes, and modify these pieces of code. Add PetRec above PlayerRec:```Public Type PetRec SpriteNum As Byte Name As String * 50 Owner As LongEnd Type```Add this to the bottom of PlayerRec:```Pet As PetRec```Add This to the bottom of TempPlayerRec:```TempPetSlot As Byte```Finally, add this to the bottom of MapNpcRec:```'Pet Data IsPet As Byte PetData As PetRec```And, Voila! You should now have a decent base to implement more features, if you wish, to this Pet System. I hope it wasn't too hard to follow, and that you learned something in the process of doing this.Regards,_**Lightning**_PS: If you include this in your game, you must place me in the game credits for my "Base Pet System", seeing as someone likes to think my work is theirs. >_> Link to comment Share on other sites More sharing options...
The New World Posted March 2, 2011 Share Posted March 2, 2011 I'm definitely going to play around with this path finding. :) Link to comment Share on other sites More sharing options...
Yxxe Posted March 2, 2011 Author Share Posted March 2, 2011 @Yukiyo:> I'm definitely going to play around with this path finding. :)Hehe, it uses the path finding already in EO, so that bit was easy. ;) Link to comment Share on other sites More sharing options...
Richy420Rich Posted March 2, 2011 Share Posted March 2, 2011 Beautiful, if I ever did create a game, I would love this. Thanks for the share Lightning. Link to comment Share on other sites More sharing options...
shado360 Posted March 2, 2011 Share Posted March 2, 2011 You are truly the best, Lightning!Thanks so much,Païn Link to comment Share on other sites More sharing options...
The New World Posted March 2, 2011 Share Posted March 2, 2011 I haven't exactly looked through this yet but, does the pet come back to it's owner's side once the target's vitals reach 0? Link to comment Share on other sites More sharing options...
Yxxe Posted March 2, 2011 Author Share Posted March 2, 2011 @n00b:> Beautiful, if I ever did create a game, I would love this. Thanks for the share Lightning.@Païn:> You are truly the best, Lightning!> > Thanks so much,> PaïnThanks! :)@Yukiyo:> I haven't exactly looked through this yet but, does the pet come back to it's owner's side once the target's vitals reach 0?Yes, once the target's vitals are reduced to zero, the pet comes back to its owner. It's only 1 line of code, calling the "PetFollow" procedure. It's easily removed. ;P Link to comment Share on other sites More sharing options...
Guest Posted March 2, 2011 Share Posted March 2, 2011 Damn, I got to brain and realised I couldn't use this D:. But thanks for releasing this :D. Link to comment Share on other sites More sharing options...
Yxxe Posted March 2, 2011 Author Share Posted March 2, 2011 @Sekaru:> Damn, I got to brain and realised I couldn't use this D:. But thanks for releasing this :D.No problem Sekaru. ;)PS: You can't have mine. ;P Link to comment Share on other sites More sharing options...
Guest Posted March 2, 2011 Share Posted March 2, 2011 Why would I need yours? You can walk down to your local Tesco's or corner shop and buy one there for under a fiver. Link to comment Share on other sites More sharing options...
Yxxe Posted March 2, 2011 Author Share Posted March 2, 2011 I updated the tutorial a few hours ago, just to let everyone know. If you followed the tutorial and an error was flagging a line including "ClearSingleMapNpc" look through the tutorial and follow the extra step I added.Thanks,Lightning Link to comment Share on other sites More sharing options...
iSKweek Posted March 2, 2011 Share Posted March 2, 2011 This. Is. Brilliant! Link to comment Share on other sites More sharing options...
Yxxe Posted March 2, 2011 Author Share Posted March 2, 2011 Thanks! :-) Link to comment Share on other sites More sharing options...
Richy420Rich Posted March 2, 2011 Share Posted March 2, 2011 I'm having an error with SpawnNpc in SpawnPet sub, invalid number of arguments. I only have npcnum and mapnum in my SpawnNPC sub. Link to comment Share on other sites More sharing options...
shadowwulf Posted March 2, 2011 Share Posted March 2, 2011 Hmm. I wonder If each player's pet can have a .dat file.Then pets can grow stronger by whatever method you use… Link to comment Share on other sites More sharing options...
Yxxe Posted March 2, 2011 Author Share Posted March 2, 2011 Oops, that is another thing I have forgotten. All you need to do is go to the spawnnpc sub, and add 2 parameters, x and y. Then go to the part where it determines the x and y coords and set a check there. Make sure the parameters are set as optional. Ill release the official code tomorrow. And rarity yes you can do that. ;-) Link to comment Share on other sites More sharing options...
Guest Posted March 2, 2011 Share Posted March 2, 2011 Added it to the index, you can stop pestering me now ;D. Link to comment Share on other sites More sharing options...
masa Posted March 2, 2011 Share Posted March 2, 2011 what is code of menu buttons?Sory i'm spanish and not speack good enlish Link to comment Share on other sites More sharing options...
Helladen Posted March 3, 2011 Share Posted March 3, 2011 Looks really good. I'm going to wait a few days until you implement all your going to do before I add it to my source. Sure showed me up, mine was too basic. :P Link to comment Share on other sites More sharing options...
Richy420Rich Posted March 3, 2011 Share Posted March 3, 2011 Well I got her working with the usual level system with random add HP and add Damage on levelup, though it's all saving in the Player(index) dat. Can't say she's really a clean code after what I did to her but lol, it's pretty cool. I could evolve the pet by using a simple level check on level up. Thanks again for the share Lightning. Link to comment Share on other sites More sharing options...
BugSICK Posted March 3, 2011 Share Posted March 3, 2011 wow this is nice very very nice, thanks again lightning, i have a suggestion can it possible 'a pet get/pick item drops? Link to comment Share on other sites More sharing options...
Yxxe Posted March 3, 2011 Author Share Posted March 3, 2011 @masa:> what is code of menu buttons?> Sory I'm spanish and not speack good enlishLike I said at the beginning of the tutorial, you require basic knowledge of the source code and VB6.@n00b:> Well I got her working with the usual level system with random add HP and add Damage on levelup, though it's all saving in the Player(index) dat. Can't say she's really a clean code after what I did to her but lol, it's pretty cool. I could evolve the pet by using a simple level check on level up. Thanks again for the share Lightning.No problem, good luck with that! ;)@BugSICK:> wow this is nice very very nice, thanks again lightning, I have a suggestion can it possible 'a pet get/pick item drops?That would be fairly simple to implement, when an npc drops its items, make then npc walk to the drops and then give the items to its owner.Also, I will be implementing 2 procedures I missed out, one of which Richy pointed out yesterday. I'll do it once I get back from college in about 5 hours. Link to comment Share on other sites More sharing options...
Yxxe Posted March 3, 2011 Author Share Posted March 3, 2011 I've now included the missing subs into the tutorial. :mad:**CLIENT**Go to modHandleData and change HandleSpawnNpc to this:```Private Sub HandleSpawnNpc(ByVal Index As Long, ByRef Data() As Byte, ByVal StartAddr As Long, ByVal ExtraVar As Long)Dim n As LongDim Buffer As clsBuffer ' If debug mode, handle error then exit out If Options.Debug = 1 Then On Error GoTo errorhandler Set Buffer = New clsBuffer Buffer.WriteBytes Data() n = Buffer.ReadLong With MapNpc(n) .Num = Buffer.ReadLong .x = Buffer.ReadLong .y = Buffer.ReadLong .Dir = Buffer.ReadLong .IsPet = Buffer.ReadByte .PetData.Name = Buffer.ReadString .PetData.Owner = Buffer.ReadLong ' Client use only .XOffset = 0 .YOffset = 0 .Moving = 0 End With ' Error handler Exit Suberrorhandler: HandleError "HandleSpawnNpc", "modHandleData", Err.Number, Err.Description, Err.Source, Err.HelpContext Err.Clear Exit SubEnd Sub```**SERVER** Go to modGameLogic and replace Sub SpawnNpc with this:```Public Sub SpawnNpc(ByVal mapNpcNum As Long, ByVal MapNum As Long, Optional ByVal SetX As Long, Optional ByVal SetY As Long) Dim Buffer As clsBuffer Dim npcNum As Long Dim i As Long Dim x As Long Dim y As Long Dim Spawned As Boolean ' Check for subscript out of range If mapNpcNum <= 0 Or mapNpcNum > MAX_MAP_NPCS Or MapNum <= 0 Or MapNum > MAX_MAPS Then Exit Sub npcNum = Map(MapNum).Npc(mapNpcNum) If npcNum > 0 Then MapNpc(MapNum).Npc(mapNpcNum).Num = npcNum MapNpc(MapNum).Npc(mapNpcNum).target = 0 MapNpc(MapNum).Npc(mapNpcNum).targetType = 0 ' clear MapNpc(MapNum).Npc(mapNpcNum).Vital(Vitals.HP) = GetNpcMaxVital(npcNum, Vitals.HP) MapNpc(MapNum).Npc(mapNpcNum).Vital(Vitals.MP) = GetNpcMaxVital(npcNum, Vitals.MP) MapNpc(MapNum).Npc(mapNpcNum).Dir = Int(Rnd * 4) 'Check if theres a spawn tile for the specific npc For x = 0 To Map(MapNum).MaxX For y = 0 To Map(MapNum).MaxY If Map(MapNum).Tile(x, y).Type = TILE_TYPE_NPCSPAWN Then If Map(MapNum).Tile(x, y).Data1 = mapNpcNum Then MapNpc(MapNum).Npc(mapNpcNum).x = x MapNpc(MapNum).Npc(mapNpcNum).y = y MapNpc(MapNum).Npc(mapNpcNum).Dir = Map(MapNum).Tile(x, y).Data2 Spawned = True Exit For End If End If Next y Next x If Not Spawned Then ' Well try 100 times to randomly place the sprite For i = 1 To 100 If SetX = 0 And SetY = 0 Then x = Random(0, Map(MapNum).MaxX) y = Random(0, Map(MapNum).MaxY) Else x = SetX y = SetY End If If x > Map(MapNum).MaxX Then x = Map(MapNum).MaxX If y > Map(MapNum).MaxY Then y = Map(MapNum).MaxY ' Check if the tile is walkable If NpcTileIsOpen(MapNum, x, y) Then MapNpc(MapNum).Npc(mapNpcNum).x = x MapNpc(MapNum).Npc(mapNpcNum).y = y Spawned = True Exit For End If Next End If ' Didn't spawn, so now we'll just try to find a free tile If Not Spawned Then For x = 0 To Map(MapNum).MaxX For y = 0 To Map(MapNum).MaxY If NpcTileIsOpen(MapNum, x, y) Then MapNpc(MapNum).Npc(mapNpcNum).x = x MapNpc(MapNum).Npc(mapNpcNum).y = y Spawned = True End If Next Next End If ' If we suceeded in spawning then send it to everyone If Spawned Then Set Buffer = New clsBuffer Buffer.WriteLong SSpawnNpc Buffer.WriteLong mapNpcNum Buffer.WriteLong MapNpc(MapNum).Npc(mapNpcNum).Num Buffer.WriteLong MapNpc(MapNum).Npc(mapNpcNum).x Buffer.WriteLong MapNpc(MapNum).Npc(mapNpcNum).y Buffer.WriteLong MapNpc(MapNum).Npc(mapNpcNum).Dir Buffer.WriteByte MapNpc(MapNum).Npc(mapNpcNum).IsPet Buffer.WriteString MapNpc(MapNum).Npc(mapNpcNum).PetData.Name Buffer.WriteLong MapNpc(MapNum).Npc(mapNpcNum).PetData.Owner SendDataToMap MapNum, Buffer.ToArray() Set Buffer = Nothing End If SendMapNpcVitals MapNum, mapNpcNum End IfEnd Sub```modPlayer, Sub PlayerWarp:```Sub PlayerWarp(ByVal Index As Long, ByVal MapNum As Long, ByVal x As Long, ByVal y As Long) Dim shopNum As Long Dim OldMap As Long Dim i As Long Dim Buffer As clsBuffer ' Check for subscript out of range If IsPlaying(Index) = False Or MapNum <= 0 Or MapNum > MAX_MAPS Then Exit Sub End If ' Check if you are out of bounds If x > Map(MapNum).MaxX Then x = Map(MapNum).MaxX If y > Map(MapNum).MaxY Then y = Map(MapNum).MaxY If x < 0 Then x = 0 If y < 0 Then y = 0 ' if same map then just send their co-ordinates If MapNum = GetPlayerMap(Index) Then SendPlayerXYToMap Index End If ' clear target TempPlayer(Index).target = 0 TempPlayer(Index).targetType = TARGET_TYPE_NONE SendTarget Index ' Save old map to send erase player data to OldMap = GetPlayerMap(Index) If OldMap <> MapNum Then Call SendLeaveMap(Index, OldMap) End If Call SetPlayerMap(Index, MapNum) Call SetPlayerX(Index, x) Call SetPlayerY(Index, y) 'If 'refreshing' map If (OldMap <> MapNum) And TempPlayer(Index).TempPetSlot > 0 Then 'switch maps PetDisband Index, OldMap SpawnPet Index, MapNum End If ' send player's equipment to new map SendMapEquipment Index ' send equipment of all people on new map If GetTotalMapPlayers(MapNum) > 0 Then For i = 1 To Player_HighIndex If IsPlaying(i) Then If GetPlayerMap(i) = MapNum Then SendMapEquipmentTo i, Index End If End If Next End If ' Now we check if there were any players left on the map the player just left, and if not stop processing npcs If GetTotalMapPlayers(OldMap) = 0 Then PlayersOnMap(OldMap) = NO ' Regenerate all NPCs' health For i = 1 To MAX_MAP_NPCS If MapNpc(OldMap).Npc(i).Num > 0 Then MapNpc(OldMap).Npc(i).Vital(Vitals.HP) = GetNpcMaxVital(MapNpc(OldMap).Npc(i).Num, Vitals.HP) End If Next End If ' Sets it so we know to process npcs on the map PlayersOnMap(MapNum) = YES TempPlayer(Index).GettingMap = YES Set Buffer = New clsBuffer Buffer.WriteLong SCheckForMap Buffer.WriteLong MapNum Buffer.WriteLong Map(MapNum).Revision SendDataTo Index, Buffer.ToArray() Set Buffer = NothingEnd Sub```Enjoy! Link to comment Share on other sites More sharing options...
jadsonkk Posted March 3, 2011 Share Posted March 3, 2011 Tanks Link to comment Share on other sites More sharing options...
jadsonkk Posted March 3, 2011 Share Posted March 3, 2011 I am brasilian, your tutorial very good!!!!! Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now