Skip to content

Commit

Permalink
feat: add game objects
Browse files Browse the repository at this point in the history
still need to figure out:
* why can they be walked through?
* interactivity
* seasonal decorations are all active simultaneously
  • Loading branch information
pikdum committed Sep 21, 2024
1 parent 97c2198 commit 94047b0
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 5 deletions.
1 change: 1 addition & 0 deletions lib/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ defmodule ThistleTea.Application do
ThistleTea.DBC,
ThistleTea.Mangos,
ThistleTea.MobSupervisor,
ThistleTea.GameObjectSupervisor,
{ThousandIsland,
port: @auth_port, handler_module: ThistleTea.Auth, handler_options: @handler_options},
{ThousandIsland,
Expand Down
3 changes: 2 additions & 1 deletion lib/behavior/follow_path.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
defmodule ThistleTea.FollowPathBehavior do
use GenServer
alias ThistleTea.Pathfinding
require Logger

defp get_next_point(state) do
Expand All @@ -22,6 +21,8 @@ defmodule ThistleTea.FollowPathBehavior do
def handle_cast(:movement_finished, %{state: :pathing} = state) do
next_point = get_next_point(state)

# TODO: handle orientation, script, text, etc.

delay =
Map.get(state, :waypoints, [])
|> Enum.at(next_point)
Expand Down
19 changes: 18 additions & 1 deletion lib/game.ex
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ defmodule ThistleTea.Game do

old_players = Map.get(state, :spawned_players, MapSet.new())
old_mobs = Map.get(state, :spawned_mobs, MapSet.new())
old_game_objects = Map.get(state, :spawned_game_objects, MapSet.new())

new_players =
SpatialHash.query(:players, state.character.map, x, y, z, 250)
Expand All @@ -367,13 +368,20 @@ defmodule ThistleTea.Game do
|> Enum.map(fn {guid, pid, _distance} -> {guid, pid} end)
|> MapSet.new()

new_game_objects =
SpatialHash.query(:game_objects, state.character.map, x, y, z, 250)
|> Enum.map(fn {guid, pid, _distance} -> {guid, pid} end)
|> MapSet.new()

players_to_remove = MapSet.difference(old_players, new_players)
mobs_to_remove = MapSet.difference(old_mobs, new_mobs)
game_objects_to_remove = MapSet.difference(old_game_objects, new_game_objects)

players_to_add = MapSet.difference(new_players, old_players)
mobs_to_add = MapSet.difference(new_mobs, old_mobs)
game_objects_to_add = MapSet.difference(new_game_objects, old_game_objects)

# TODO: update a reverse mapping, so mobs know nearby players
# TODO: update a reverse mapping, so mobs know nearby players?
for {guid, pid} <- players_to_remove do
if pid != self() do
send_packet(@smsg_destroy_object, <<guid::little-size(64)>>)
Expand All @@ -385,6 +393,10 @@ defmodule ThistleTea.Game do
GenServer.cast(pid, :try_sleep)
end

for {guid, _pid} <- game_objects_to_remove do
send_packet(@smsg_destroy_object, <<guid::little-size(64)>>)
end

for {_guid, pid} <- players_to_add do
if pid != self() do
GenServer.cast(pid, {:send_update_to, self()})
Expand All @@ -396,6 +408,10 @@ defmodule ThistleTea.Game do
GenServer.cast(pid, {:send_update_to, self()})
end

for {_guid, pid} <- game_objects_to_add do
GenServer.cast(pid, {:send_update_to, self()})
end

# TODO: redundant, refactor out?
player_pids = new_players |> Enum.map(fn {_guid, pid} -> pid end)
mob_pids = new_mobs |> Enum.map(fn {_guid, pid} -> pid end)
Expand All @@ -404,6 +420,7 @@ defmodule ThistleTea.Game do
Map.merge(state, %{
spawned_players: new_players,
spawned_mobs: new_mobs,
spawned_game_objects: new_game_objects,
player_pids: player_pids,
mob_pids: mob_pids
})
Expand Down
79 changes: 79 additions & 0 deletions lib/game/game_object.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
defmodule ThistleTea.GameObject do
use GenServer
import ThistleTea.Game.UpdateObject, only: [generate_packet: 4]
import Bitwise, only: [|||: 2]
require Logger

@game_object_guid_offset 0xF1100000

@update_type_create_object2 3
@object_type_game_object 5

@update_flag_all 0x10
@update_flag_has_position 0x40

def start_link(game_object) do
GenServer.start_link(__MODULE__, game_object)
end

def update_packet(state) do
fields =
%{
object_guid: state.game_object.guid,
object_type: @object_type_game_object,
object_entry: state.game_object.id,
object_scale_x: state.game_object.game_object_template.size,
game_object_display_id: state.game_object.game_object_template.display_id,
game_object_flags: state.game_object.game_object_template.flags,
game_object_rotation0: state.game_object.rotation0,
game_object_rotation1: state.game_object.rotation1,
game_object_rotation2: state.game_object.rotation2,
game_object_rotation3: state.game_object.rotation3,
game_object_state: state.game_object.state,
game_object_pos_x: state.game_object.position_x,
game_object_pos_y: state.game_object.position_y,
game_object_pos_z: state.game_object.position_z,
game_object_type_id: state.game_object.game_object_template.type,
game_object_animprogress: state.game_object.animprogress
}

mb = %{
update_flag: @update_flag_all ||| @update_flag_has_position,
x: state.game_object.position_x,
y: state.game_object.position_y,
z: state.game_object.position_z,
orientation: state.game_object.orientation
}

generate_packet(@update_type_create_object2, @object_type_game_object, fields, mb)
end

@impl GenServer
def handle_cast({:send_update_to, pid}, state) do
Logger.debug("Spawning object: #{state.game_object.game_object_template.name}")
packet = update_packet(state)
GenServer.cast(pid, {:send_update_packet, packet})
{:noreply, state}
end

@impl GenServer
def init(game_object) do
game_object = Map.put(game_object, :guid, game_object.guid + @game_object_guid_offset)

SpatialHash.update(
:game_objects,
game_object.guid,
self(),
game_object.map,
game_object.position_x,
game_object.position_y,
game_object.position_z
)

state = %{
game_object: game_object
}

{:ok, state}
end
end
13 changes: 11 additions & 2 deletions lib/game/mob.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule ThistleTea.Mob do
require Logger

# prevent collisions with player guids
@creature_guid_offset 0x100000
@creature_guid_offset 0xF1300000

# @update_type_movement 1
@update_type_create_object2 3
Expand Down Expand Up @@ -400,12 +400,21 @@ defmodule ThistleTea.Mob do
end
end

defp get_scale(state) do
# no idea why it's like this
if state.creature.creature_template.scale > 0 do
state.creature.creature_template.scale
else
1.0
end
end

def update_packet(state, movement_flags \\ 0) do
fields = %{
object_guid: state.creature.guid,
object_type: 9,
object_entry: state.creature.id,
object_scale_x: 1.0,
object_scale_x: get_scale(state),
unit_health: state.creature.curhealth,
unit_power_1: state.creature.curmana,
unit_max_health: state.max_health,
Expand Down
2 changes: 1 addition & 1 deletion lib/game/query.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule ThistleTea.Game.Query do
require Logger

# prevent collisions with player guids
@creature_guid_offset 0x100000
@creature_guid_offset 0xF1300000

@cmsg_name_query 0x050
@smsg_name_query_response 0x051
Expand Down
60 changes: 60 additions & 0 deletions lib/game/update_object.ex
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,54 @@ defmodule ThistleTea.Game.UpdateObject do
player_rest_state_experience: %{
size: 1,
offset: 0x497
},
game_object_display_id: %{
size: 1,
offset: 0x008
},
game_object_flags: %{
size: 1,
offset: 0x009
},
game_object_rotation0: %{
size: 1,
offset: 0x00A
},
game_object_rotation1: %{
size: 1,
offset: 0x00B
},
game_object_rotation2: %{
size: 1,
offset: 0x00C
},
game_object_rotation3: %{
size: 1,
offset: 0x00D
},
game_object_state: %{
size: 1,
offset: 0x00E
},
game_object_pos_x: %{
size: 1,
offset: 0x00F
},
game_object_pos_y: %{
size: 1,
offset: 0x010
},
game_object_pos_z: %{
size: 1,
offset: 0x011
},
game_object_type_id: %{
size: 1,
offset: 0x015
},
game_object_animprogress: %{
size: 1,
offset: 0x018
}
}

Expand Down Expand Up @@ -583,6 +631,18 @@ defmodule ThistleTea.Game.UpdateObject do
:player_xp -> <<value::little-size(32)>>
:player_next_level_xp -> <<value::little-size(32)>>
:player_rest_state_experience -> <<value::little-size(32)>>
:game_object_display_id -> <<value::little-size(32)>>
:game_object_flags -> <<value::little-size(32)>>
:game_object_rotation0 -> <<value::little-float-size(32)>>
:game_object_rotation1 -> <<value::little-float-size(32)>>
:game_object_rotation2 -> <<value::little-float-size(32)>>
:game_object_rotation3 -> <<value::little-float-size(32)>>
:game_object_state -> <<value::little-size(32)>>
:game_object_pos_x -> <<value::little-float-size(32)>>
:game_object_pos_y -> <<value::little-float-size(32)>>
:game_object_pos_z -> <<value::little-float-size(32)>>
:game_object_type_id -> <<value::little-size(32)>>
:game_object_animprogress -> <<value::little-size(32)>>
_ -> raise "Unknown field: #{field}"
end
end)
Expand Down
26 changes: 26 additions & 0 deletions lib/mangos/game_object.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule GameObject do
use Ecto.Schema

@primary_key {:guid, :integer, autogenerate: false}
schema "gameobject" do
field(:id, :integer, default: 0)
field(:map, :integer, default: 0)
field(:position_x, :float, default: 0.0)
field(:position_y, :float, default: 0.0)
field(:position_z, :float, default: 0.0)
field(:orientation, :float, default: 0.0)
field(:rotation0, :float, default: 0.0)
field(:rotation1, :float, default: 0.0)
field(:rotation2, :float, default: 0.0)
field(:rotation3, :float, default: 0.0)
field(:spawntimesecs, :integer, default: 0)
field(:animprogress, :integer, default: 0)
field(:state, :integer, default: 0)

belongs_to(:game_object_template, GameObjectTemplate,
foreign_key: :id,
references: :entry,
define_field: false
)
end
end
39 changes: 39 additions & 0 deletions lib/mangos/game_object_template.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
defmodule GameObjectTemplate do
use Ecto.Schema

@primary_key {:entry, :integer, autogenerate: false}
schema "gameobject_template" do
field(:type, :integer, default: 0)
field(:display_id, :integer, source: :displayId, default: 0)
field(:name, :string, default: "")
field(:faction, :integer, default: 0)
field(:flags, :integer, default: 0)
field(:size, :float, default: 1.0)
field(:data0, :integer, default: 0)
field(:data1, :integer, default: 0)
field(:data2, :integer, default: 0)
field(:data3, :integer, default: 0)
field(:data4, :integer, default: 0)
field(:data5, :integer, default: 0)
field(:data6, :integer, default: 0)
field(:data7, :integer, default: 0)
field(:data8, :integer, default: 0)
field(:data9, :integer, default: 0)
field(:data10, :integer, default: 0)
field(:data11, :integer, default: 0)
field(:data12, :integer, default: 0)
field(:data13, :integer, default: 0)
field(:data14, :integer, default: 0)
field(:data15, :integer, default: 0)
field(:data16, :integer, default: 0)
field(:data17, :integer, default: 0)
field(:data18, :integer, default: 0)
field(:data19, :integer, default: 0)
field(:data20, :integer, default: 0)
field(:data21, :integer, default: 0)
field(:data22, :integer, default: 0)
field(:data23, :integer, default: 0)
field(:mingold, :integer, default: 0)
field(:maxgold, :integer, default: 0)
end
end
34 changes: 34 additions & 0 deletions lib/state/game_object_supervisor.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule ThistleTea.GameObjectSupervisor do
use Supervisor
import Ecto.Query
alias ThistleTea.Mangos
require Logger

def start_link(args) do
Supervisor.start_link(__MODULE__, args, name: __MODULE__)
end

@impl Supervisor
def init(_args) do
query =
from(g in GameObject,
join: gt in assoc(g, :game_object_template),
preload: [game_object_template: gt],
select: g
)

children =
Mangos.all(query)
|> Enum.map(fn game_object ->
%{
id: {ThistleTea.GameObject, game_object.guid},
start: {ThistleTea.GameObject, :start_link, [game_object]}
}
end)

Logger.info("Spawned #{length(children)} game objects.")

opts = [strategy: :one_for_one, max_restarts: 100]
Supervisor.init(children, opts)
end
end

0 comments on commit 94047b0

Please sign in to comment.