From 983ede26330fcea123e20b72e81cd627d35e6e24 Mon Sep 17 00:00:00 2001 From: Torwent Date: Mon, 16 Dec 2024 17:33:22 +0100 Subject: [PATCH] feat(housemap): add the new housemap Basically a full rewrite of `TPOHMap` renamed to `THouseMap` because I like it more and keeps backwards compatibility. This is still not usable on it's own for anything useful, will require a small rewrite of the `TPOHHandler`, which will be named `THouseHandler` with the global variable of `House`. Even if it's not usable yet to walk around the house, you may use it to map your house in advance if you want. --- optional/handlers/housemap.simba | 1325 ++++++++++++++++++++++++++++++ osr/walker/house.png | Bin 1648 -> 1282 bytes osr/walker/house_icons.png | Bin 0 -> 12667 bytes 3 files changed, 1325 insertions(+) create mode 100644 optional/handlers/housemap.simba create mode 100644 osr/walker/house_icons.png diff --git a/optional/handlers/housemap.simba b/optional/handlers/housemap.simba new file mode 100644 index 00000000..01ad1429 --- /dev/null +++ b/optional/handlers/housemap.simba @@ -0,0 +1,1325 @@ +(* +# POHMap +The POH Map is what's responsible for mapping a user's POH. +*) + +{$DEFINE WL_HOUSEMAP_INCLUDED} +{$IFNDEF WL_OSR} + {$I WaspLib/osr.simba} +{$ENDIF} + +type +(* +(EHouseRoom)= +## type EHouseRoom +```pascal +EHouseRoom = ( + UNKNOWN, GARDEN, SUPERIOR_GARDEN, MENAGERIE_OPEN, MENAGERIE_CLOSED, + STUDY_PARLOUR, KITCHEN_BEDROOM, ACHIEVEMENT_GALLERY, QUEST_NEXUS, COMBAT, + COSTUME, ALTAR, PORTAL, WORKSHOP +); +``` +*) +{$SCOPEDENUMS ON} + EHouseRoom = ( + UNKNOWN, PARLOUR, GARDEN, KITCHEN, DINING, WORKSHOP, BEDROOM, SKILL_HALL, + LEAGUE_HALL, GAMES, COMBAT, QUEST_HALL, MENAGERIE_OUTDOORS, + MENAGERIE_INDOORS, STUDY, COSTUME, CHAPEL, PORTAL_CHAMBER, FORMAL_GARDEN, + THRONE, SUPERIOR_GARDEN, PORTAL_NEXUS, ACHIEVEMENT_GALLERY + ); + + EHouseDecoration = ( + WOOD, STONE, WHITE, FREMENNIK, TROPICAL, FANCY, DEATHLY, TWISTED, HOSIDIUS, WINTER + ); + + EHouseObject = ( + EXIT, REPAIR_STAND, SERVANT_BAG, + GLORY, MYTH_CAPE, LECTERN, PRAYER_ALTAR, + + DIGSITE_PENDANT, XERIC_TALISMAN, NEXUS, + RESTORATION_POOL, REVITALISATION_POOL, REJUVENATION_POOL, FANCY_POOL, ORNATE_POOL, + SPIRIT_TREE, OBELISK, FAIRY_RING, FAIRY_TREE, + + BASIC_JEWELLERY_BOX, FANCY_JEWELLERY_BOX, ORNATE_JEWELLERY_BOX, + ANCIENT_ALTAR, LUNAR_ALTAR, DARK_ALTAR, OCCULT_ALTAR, + CAPE_HANGER + ); + + EHousePortal = ( + EMPTY, ARCEUUS_LIBRARY, DRAYNOR_MANOR, BATTLEFRONT, VARROCK, GRAND_EXCHANGE, + MIND_ALTAR, LUMBRIDGE, FALADOR, SALVE_GRAVEYARD, CAMELOT, SEERS_VILLAGE, + FENKENSTRAINS_CASTLE, KOUREND_CASTLE, EAST_ARDOUGNE, CIVITAS_ILLA_FORTIS, + WATCHTOWER, YANILLE, SENNTISTEN, WEST_ARDOUGNE, MARIM, HARMONY_ISLAND, + KHARYRLL, MOONCLAN, CEMETERY, WATERBIRTH_ISLAND, BARROWS, CARRALLANGER, + FISHING_GUILD, CATHERBY, ANNAKARL, APE_ATOLL_DUNGEON, GHORROCK, + TROLL_STRONGHOLD, WEISS + ); + + +{$SCOPEDENUMS OFF} + + THouseRoom = record + Room: EHouseRoom; + Rotation: Int32; + Objects: set of EHouseObject; + end; + + TPortalChamber = record + Left, Middle, Right: EHousePortal; + end; + +(* +(THouseMap)= +## type THouseMap +```pascal +type + THouseMap = record + AMOUNT, SIZE: Int32; + Map: TMufasaBitmap; + + Rooms: array of array of THouseRoom; + + //helpers: + PortalChambers: array of array of TPortalChamber; + + RoomsBitmap, IconBitmap: TMufasaBitmap; + RoomBitmaps, IconBitmaps: array [EHouseRoom] of TMufasaBitmap; + + Selected: record + Matrix, Map: TPoint; + end; + HouseBounds: TBox; + + Decoration: EHouseDecoration; + Colors: record + Outdoors, Indoors, Dungeon: Int32; + end; + Config: TConfigJSON; + end; +``` +Helper record used by the {ref}`TRSPOHHandler`. +All `THouseMap` methods are helper methods for the {ref}`TRSPOHHandler` and you shouldn't have to call them for anything. +*) + THouseMap = record + AMOUNT, SIZE: Int32; + Map: TMufasaBitmap; + + Rooms: array of array of THouseRoom; + + //helpers: + PortalChambers: array of array of TPortalChamber; + + RoomsBitmap, IconBitmap: TMufasaBitmap; + RoomBitmaps, IconBitmaps: array [EHouseRoom] of TMufasaBitmap; + + Selected: record + Matrix, Map: TPoint; + end; + HouseBounds: TBox; + + Decoration: EHouseDecoration; + Colors: record + Outdoors, Indoors, Dungeon: Int32; + end; + Config: TConfigJSON; + end; + +var + HousePools: set of EHouseObject = [EHouseObject.RESTORATION_POOL..EHouseObject.ORNATE_POOL]; + SuperiorGardenTeleports: set of EHouseObject = [EHouseObject.SPIRIT_TREE..EHouseObject.FAIRY_TREE]; + JewelleryBoxes: set of EHouseObject = [EHouseObject.BASIC_JEWELLERY_BOX..EHouseObject.ORNATE_JEWELLERY_BOX]; + MagicAltars: set of EHouseObject = [EHouseObject.ANCIENT_ALTAR..EHouseObject.OCCULT_ALTAR]; + +(* +## THouseMap.Free() +```pascal +procedure THouseMap.Free(); +``` +Internal method automatically called for your on script termination. +You do not have to call it yourself. +*) +procedure THouseMap.Free(); +var + room: EHouseRoom; +begin + Self.Map.Free(); + Self.RoomsBitmap.Free(); + Self.IconBitmap.Free(); + for room := Low(EHouseRoom) to High(EHouseRoom) do + begin + if Self.RoomBitmaps[room] <> nil then Self.RoomBitmaps[room].Free(); + if Self.IconBitmaps[room] <> nil then Self.IconBitmaps[room].Free(); + end; + + Self.Rooms := []; +end; + +function THouseMap.ToEncodedString(): String; forward; +procedure THouseMap.LoadFromString(s: String); forward; +procedure THouseMap.DrawMap(room: THouseRoom; p: TPoint); forward; + +(* +## THouseMap.Init() +```pascal +procedure THouseMap.Init(size, amount: Int32); +``` +Internal method automatically called for your on script startup along with POH.Init(). +You do not have to call it yourself. +*) +procedure THouseMap.Init(size, amount: Int32); +begin + Self.Config.Setup('pohhandler'); + + if not Self.Config.Has('size') then Self.Config.Put('size', size); + if not Self.Config.Has('amount') then Self.Config.Put('amount', amount); + + Self.SIZE := Self.Config.GetInt('size'); + Self.AMOUNT := Self.Config.GetInt('amount'); + + Self.Selected.Map := [Self.AMOUNT div 2 * Self.SIZE, Self.AMOUNT div 2 * Self.SIZE]; + Self.Selected.Matrix := [Self.AMOUNT div 2, Self.AMOUNT div 2]; + Self.HouseBounds := [Self.AMOUNT div 2, Self.AMOUNT div 2, Self.AMOUNT div 2, Self.AMOUNT div 2]; + + Self.Map.Init(); + Self.Map.SetSize(Self.SIZE * Self.AMOUNT, Self.SIZE * Self.AMOUNT); + + Self.RoomsBitmap.Init(); + Self.RoomsBitmap.LoadFromFile(WALKER_DIR + 'house.png'); + + Self.IconBitmap.Init(); + Self.IconBitmap.LoadFromFile(WALKER_DIR + 'house_icons.png'); + + AddOnTerminate(@Self.Free); + + SetLength(Self.Rooms, Self.AMOUNT, Self.AMOUNT); + SetLength(Self.PortalChambers, Self.AMOUNT, Self.AMOUNT); + + if Self.Config.Has('layout') then + Self.LoadFromString(Self.Config.GetString('layout')) + else + Self.DrawMap([EHouseRoom.GARDEN, 0, []], [Self.AMOUNT div 2, Self.AMOUNT div 2]); +end; + +function THouseMap.IsOutdoors(room: EHouseRoom): Boolean; +begin + case room of + EHouseRoom.UNKNOWN, EHouseRoom.GARDEN, EHouseRoom.FORMAL_GARDEN, + EHouseRoom.SUPERIOR_GARDEN, EHouseRoom.MENAGERIE_OUTDOORS: Exit(True); + end; +end; + +procedure THouseMap.SetColors(decoration: EHouseDecoration); +begin + Self.Decoration := decoration; + + case decoration of + EHouseDecoration.WOOD, EHouseDecoration.TROPICAL: + begin + Self.Colors.Outdoors := $2D9167; + Self.Colors.Indoors := $59808F; + end; + + EHouseDecoration.STONE: + begin + Self.Colors.Outdoors := $2D9167; + Self.Colors.Indoors := $42617E; + end; + + EHouseDecoration.WHITE: + begin + Self.Colors.Outdoors := $82C4D1; + Self.Colors.Indoors := $70969B; + end; + + EHouseDecoration.FREMENNIK: + begin + Self.Colors.Outdoors := $4C707B; + Self.Colors.Indoors := $457083; + end; + + EHouseDecoration.FANCY: + begin + Self.Colors.Outdoors := $2D9167; + Self.Colors.Indoors := $789EA6; + end; + + EHouseDecoration.DEATHLY, EHouseDecoration.HOSIDIUS: + begin + Self.Colors.Outdoors := $2D9167; + Self.Colors.Indoors := $5786A7; + end; + + EHouseDecoration.TWISTED: + begin + Self.Colors.Outdoors := $1B1B20; + Self.Colors.Indoors := $545459; + end; + + EHouseDecoration.WINTER: + begin + Self.Colors.Outdoors := $D2D2D7; + Self.Colors.Indoors := $5786A7; + end; + end; +end; + +function THouseMap.GetColor(room: EHouseRoom): Int32; +begin + if Self.IsOutdoors(room) then Exit(Self.Colors.Outdoors); + Result := Self.Colors.Indoors; +end; + +(* +## THouseMap.GetRoomBitmapBox() +```pascal +function THouseMap.GetRoomBitmapBox(room: EHouseRoom): TBox; +``` +Internal method used to get the box of the {ref}`EHouseRoom` you pass in. + +This box is a box of the following image: + +![poh rooms](../../../osr/walker/poh.png) + +Example: +```pascal +{$I WaspLib/optional/handlers/poh.simba} +begin + WriteLn POH.Map.GetRoomBitmapBox(EHouseRoom.SUPERIOR_GARDEN); +end; +``` +*) +function THouseMap.GetRoomBitmapBox(room: EHouseRoom): TBox; +var + i: Int32; +begin + i := Ord(room); + Result := [i * Self.SIZE, 0, i * Self.SIZE + Self.SIZE - 1, Self.SIZE-1]; +end; + +(* +## THouseMap.GetRoomBitmap() +```pascal +function THouseMap.GetRoomBitmap(room: EHouseRoom; color: Int32 = -1): TMufasaBitmap; +``` +Internal method used to retrieve a bitmap of the {ref}`EHouseRoom` you pass in. + +Example: +```pascal +{$I WaspLib/optional/handlers/poh.simba} +var + bmp: TMufasaBitmap; +begin + bmp := POH.Map.GetRoomBitmap(EHouseRoom.SUPERIOR_GARDEN); + bmp.Debug(); + bmp.Free(); +end; +``` +*) +function THouseMap.GetRoomBitmap(room: EHouseRoom): TMufasaBitmap; +var + b: TBox; +begin + if Self.RoomBitmaps[room] = nil then + begin + b := Self.GetRoomBitmapBox(room); + Result := Self.RoomsBitmap.Copy(b.X1, b.Y1, b.X2, b.Y2); + Self.RoomBitmaps[room] := Result.Copy(); + end + else + Result := Self.RoomBitmaps[room].Copy(); + + Result.ReplaceColor($FFFFFF, Self.GetColor(room)); +end; + +function THouseMap.GetIconBitmap(room: EHouseRoom): TMufasaBitmap; +var + b: TBox; +begin + if Self.IconBitmaps[room] = nil then + begin + b := Self.GetRoomBitmapBox(room); + Result := Self.IconBitmap.Copy(b.X1, b.Y1, b.X2, b.Y2); + Self.IconBitmaps[room] := Result.Copy(); + end + else + Result := Self.IconBitmaps[room].Copy(); + + Result.ReplaceColor($FFFFFF, Self.GetColor(room)); +end; + + +(* +## THouseMap.WriteRoom() +```pascal +procedure THouseMap.WriteRoom(room: EHouseRoom; index: TPoint); +``` +Internal method used to write a room to `THouseMap.Rooms` cache. +This uses an `TPoint` as a room `index` in a 2D array of {ref}`EHouseRoom`. + +Unless you know what you are doing, you definitly should not use this for anything. + +Example: +```pascal +POH.Map.WriteRoom(EHouseRoom.SUPERIOR_GARDEN, [3,3]); +``` +*) +procedure THouseMap.WriteRoom(room: THouseRoom; index: TPoint); +begin + Self.Rooms[index.Y,index.X] := room; + if room.Room <> EHouseRoom.PORTAL_CHAMBER then + Self.PortalChambers[index.Y,index.X] := []; + Self.Config.Put('layout', Self.ToEncodedString()); +end; + +(* +## THouseMap.ReadRoom() +```pascal +function THouseMap.ReadRoom(index: TPoint): EHouseRoom; +``` +Internal method used to read a cached room in `THouseMap.Rooms`. +This uses an `TPoint` as a room `index`. + +Unless you know what you are doing, you don't need this, but there's no harm in using it. + +Example: +```pascal +WriteLn POH.Map.ReadRoom([3,3]); +``` +*) +function THouseMap.ReadRoom(index: TPoint): THouseRoom; +begin + Result := Self.Rooms[index.Y,index.X]; +end; + +(* +## THouseMap.PrintRooms() +```pascal +procedure THouseMap.PrintRooms(); +``` +Debugging helper method used to read a cached rooms in `THouseMap.Rooms`. +This will print the whole cache nicely formated in a way that is human friendly like you were looking at the house map. + +Unless you know what you are doing, you don't need this, but there's no harm in using it. + +```{note} +:class: dropdown +It's a extremely useful debugging tool when paired with `POH.Map.Map.Debug()`. +``` + +Example: +```pascal +POH.Setup(); +POH.Map.PrintRooms(); +``` +*) +procedure THouseMap.PrintRooms(); +var + str: String; + i, j: Int32; +begin + str := '[' + LineEnding; + for i := 0 to High(Self.Rooms) do + begin + for j := 0 to High(Self.Rooms[i]) do + begin + if j = 0 then + begin + str += ' ['; + end; + str += ToStr(Self.Rooms[i][j]); + if j < High(Self.Rooms[i]) then + str += ', '; + end; + str += ', ' + LineEnding; + end; + str += ']'; + WriteLn str; +end; + + +(* +## THouseMap.DrawMap() +```pascal +procedure THouseMap.DrawMap(bmp: TMufasaBitmap; room: EHouseRoom; p: TPoint); +procedure THouseMap.DrawMap(room: EHouseRoom; color: Int32; p: TPoint); overload; +``` +Methods used to draw the POH map and cache the rooms drawn in `THouseMap.Rooms`. + +Example: +```pascal +POH.Map.DrawMap(EHouseRoom.SUPERIOR_GARDEN, POH.GrassColor, [3,3]); +POH.Map.Debug(); +POH.Map.PrintRooms(); +``` +*) +procedure THouseMap.DrawMap(bmp: TMufasaBitmap; room: THouseRoom; index: TPoint); overload; +var + tmp: TMufasaBitmap; +begin + Self.WriteRoom(room, index); + + tmp := bmp.RotateClockWise(room.Rotation); + Self.Map.DrawBitmap(tmp, [Self.SIZE * index.X, Self.SIZE * index.Y]); + tmp.Free(); + bmp.Free(); + + if (index.X > Self.HouseBounds.X1) and (index.X < Self.HouseBounds.X2) and + (index.Y > Self.HouseBounds.Y1) and (index.Y < Self.HouseBounds.Y2) then + Exit; + + if index.X <= Self.HouseBounds.X1 then Self.HouseBounds.X1 := index.X-1; + if index.X >= Self.HouseBounds.X2 then Self.HouseBounds.X2 := index.X+1; + if index.Y <= Self.HouseBounds.Y1 then Self.HouseBounds.Y1 := index.Y-1; + if index.Y >= Self.HouseBounds.Y2 then Self.HouseBounds.Y2 := index.Y+1; + + tmp := Self.GetRoomBitmap(EHouseRoom.UNKNOWN); + for index.Y := Max(Self.HouseBounds.Y1, 0) to Min(Self.HouseBounds.Y2, Self.AMOUNT-1) do + for index.X := Max(Self.HouseBounds.X1, 0) to Min(Self.HouseBounds.X2, Self.AMOUNT-1) do + begin + if Self.ReadRoom(index).Room <> EHouseRoom.UNKNOWN then Continue; + Self.Map.DrawBitmap(tmp, [Self.SIZE * index.X, Self.SIZE * index.Y]); + end; + tmp.Free(); +end; + +procedure THouseMap.DrawMap(room: THouseRoom; p: TPoint); +var + bmp: TMufasaBitmap; +begin + bmp := Self.GetRoomBitmap(room.Room); + Self.DrawMap(bmp, room, p); +end; + + +procedure THouseMap.Redraw(); +var + bmp, tmp: TMufasaBitmap; + index: TPoint; + room: THouseRoom; +begin + for index.Y := Max(Self.HouseBounds.Y1, 0) to Min(Self.HouseBounds.Y2, Self.AMOUNT-1) do + for index.X := Max(Self.HouseBounds.X1, 0) to Min(Self.HouseBounds.X2, Self.AMOUNT-1) do + begin + room := Self.ReadRoom(index); + bmp := Self.GetRoomBitmap(room.Room); + + tmp := bmp.RotateClockWise(room.Rotation); + Self.Map.DrawBitmap(tmp, [Self.SIZE * index.X, Self.SIZE * index.Y]); + tmp.Free(); + bmp.Free(); + end; +end; + + + +function THouseMap.ToEncodedString(): String; +var + idx: TPoint; + room: THouseRoom; + str, portals: String; + o: EHouseObject; + chamber: TPortalChamber; +begin + Result := '[' + ToStr(Ord(Self.Decoration)) +']'; + for idx.Y := 0 to Self.AMOUNT-1 do + for idx.X := 0 to Self.AMOUNT-1 do + begin + room := Self.ReadRoom(idx); + + for o in room.Objects do str += ToStr(Ord(o)) + ' '; + str := str.Trim(); + + if room.Room = EHouseRoom.PORTAL_CHAMBER then + begin + chamber := Self.PortalChambers[idx.Y, idx.X]; + portals := '(' + ToStr(Ord(chamber.Left)) + ' ' + + ToStr(Ord(chamber.Middle)) + ' ' + + ToStr(Ord(chamber.Right)) + + ')'; + end; + + Result += '[' + ToStr(idx.X) + ' ' + ToStr(idx.Y) + ' ' + ToStr(Ord(room.Room)) + ' ' + ToStr(room.Rotation); + if str <> '' then Result += ' ' + str; + if portals <> '' then Result += ' ' + portals; + Result += ']'; + + str := ''; + portals := ''; + end; + + Result := Base64Encode(CompressString(Result)); +end; + +procedure THouseMap.LoadFromString(s: String); +var + idx: TPoint; + elements, values: TStringArray; + i, j: Int32; + room: THouseRoom; + main, portals: String; +begin + elements := MultiBetween(DecompressString(Base64Decode(s)), '[', ']'); + + if elements <> [] then + Self.SetColors(EHouseDecoration(StrToInt(elements[0]))); + + for i := 1 to High(elements) do + begin + if elements[i].Contains(' (') then + begin + main := elements[i].Before(' ('); + portals := elements[i].Between('(', ')'); + end + else + begin + main := elements[i]; + portals := ''; + end; + + values := Explode(' ', main); + idx := [StrToInt(values[0]), StrToInt(values[1])]; + + room.Room := EHouseRoom(StrToInt(values[2])); + room.Rotation := StrToInt(values[3]); + + room.Objects := []; + for j := 4 to High(values) do + room.Objects += EHouseObject(StrToInt(values[j])); + + case room.Room of + EHouseRoom.UNKNOWN: ; + EHouseRoom.PORTAL_CHAMBER: + begin + values := Explode(' ', portals); + + Self.PortalChambers[idx.Y, idx.X] := [ + EHousePortal(StrToInt(values[0])), + EHousePortal(StrToInt(values[1])), + EHousePortal(StrToInt(values[2])) + ]; + + Self.DrawMap(room, idx); + end; + else Self.DrawMap(room, idx); + end; + end; +end; + + + +(* +## THouseMap.GetPointIndex() +```pascal +function THouseMap.GetPointIndex(p: TPoint): TPoint; +``` +Helper method that converts a normal TPoint to a index used by {ref}`THouseMap.ReadRoom()`. + +Example: +```pascal +WriteLn POH.Map.GetPointIndex(POH.GetPos()); +``` +*) +function THouseMap.GetPointIndex(p: TPoint): TPoint; +begin + Result := [p.X div Self.SIZE, p.Y div Self.SIZE]; +end; + +(* +## THouseMap.GetRoom() +```pascal +function THouseMap.GetRoom(p: TPoint): EHouseRoom; +``` +Helper method that returns the cached room in `THouseMap.Rooms`with the help of +{ref}`THouseMap.GetPointIndex()` and {ref}`THouseMap.ReadRoom()`. + +Example: +```pascal +WriteLn POH.Map.GetRoom(POH.GetPos()); +``` +*) +function THouseMap.GetRoom(p: TPoint): EHouseRoom; +begin + Result := Self.ReadRoom(Self.GetPointIndex(p)).Room; +end; + + +(* +## THouseMap.SampleSearch() +```pascal +function THouseMap.SampleSearch(minimapBMP: TMufasaBitmap; sampleSize: Int32 = 50; sampleAmount: Int32 = 3): TPoint; +``` +Helper method that returns the the position of the minimapBMP in the `THouseMap.Map`, essentially getting the player position. + +Example: +```pascal +{$I WaspLib/optional/handlers/poh.simba} +var + minimapBMP: TMufasaBitmap; +begin + minimapBMP := TRSPOHHandler.GetCleanMinimap(); + minimapBMP.ReplaceColor(1, POH.Map.GrassColor); + WriteLn POH.Map.SampleSearch(minimapBMP, SAMPLE_SIZE); + minimapBMP.Free(); +end; +``` +*) +function THouseMap.SampleSearch(minimapBMP: TMufasaBitmap; sampleSize: Int32 = 50; sampleAmount: Int32 = 3): TPoint; +var + sampleSM, sampleLG: TMufasaBitmap; + offset: TPoint; + b: TBox; + matrixSM, matrixLG: TSingleMatrix; + resultSM, resultLG: TPointArray; + i, j, s: Int32; + p: TPoint; +begin + s := 20; + offset := minimapBMP.getCenter().Offset(2, 5); + b := Box(offset, s, s); + sampleSM := minimapBMP.Copy(b.X1, b.Y1, b.X2, b.Y2); + b := Box(offset, sampleSize, sampleSize); + sampleLG := minimapBMP.Copy(b.X1, b.Y1, b.X2, b.Y2); + + matrixSM := Self.Map.MatchTemplate(sampleSM, TM_CCOEFF_NORMED); + matrixLG := Self.Map.MatchTemplate(sampleLG, TM_CCOEFF_NORMED); + + sampleSM.Free(); + sampleLG.Free(); + + Dec(sampleAmount); + + for i := 0 to sampleAmount do + with matrixSM.ArgMax() do + begin + p := Point(X - (sampleSize - s), Y - (sampleSize - s)).Offset(sampleSize - 3, sampleSize - 5); + if Self.GetRoom(p) <> EHouseRoom.UNKNOWN then + resultSM += p + else + Dec(i); + matrixSM.Fill([X - 5, Y - 5, X + 5, Y + 5], 0); + end; + + for i := 0 to sampleAmount do + with matrixLG.ArgMax() do + begin + p := Point(X, Y).Offset(sampleSize - 3, sampleSize - 5); + if Self.GetRoom(p) <> EHouseRoom.UNKNOWN then + resultLG += p + else + Dec(i); + matrixLG.Fill([X - 5, Y - 5, X + 5, Y + 5], 0); + end; + + for i := 0 to High(resultLG) do + for j := 0 to High(resultSM) do + if resultLG[i].InRange(resultSM[j], 6) then + Exit(resultLG[i]); + + Result := resultSM[0]; +end; + + +var + HouseMap: THouseMap; + +{%codetools off} +procedure TScriptForm.HousePaintArea(sender: TObject; canvas: TCanvas; r: TRect); +var + limit, b: TBox; + tpa: TPointArray; + p: TPoint; + pixel: Int32; +begin + limit := [r.Left, r.Top, r.Right, r.Bottom]; + p := [HouseMap.Selected.Matrix.X*HouseMap.SIZE, HouseMap.Selected.Matrix.Y*HouseMap.SIZE]; + b := [p.X, p.Y, p.X+HouseMap.SIZE-1, p.Y+HouseMap.SIZE-1]; + if not limit.Contains(b) and not limit.Overlap(b) then Exit; + + tpa := b.ToTPA().Connect(); + for p in tpa do + begin + if not limit.Contains(p) then Continue; + pixel := TSimbaImageBox(sender).getBackground().getCanvas().GetPixel(p.X, p.Y); + if pixel = $0000EE then Continue; + canvas.SetPixel(p.X, p.Y, $00FFFF); + end; + + for p in tpa.ShapeFill().Difference(tpa) do + begin + if not limit.Contains(p) then Continue; + pixel := TSimbaImageBox(sender).getBackground().getCanvas().GetPixel(p.X, p.Y); + if pixel = $0000EE then Continue; + canvas.SetPixel(p.X, p.Y, SRL.CalculateTransparency(pixel, $00FFFF, 0.5, False)); + end; +end; + +procedure TMufasaBitmap._DrawPOH(); +const + ROTATIONS: TPointArray = [[0,0], [31,0], [31,31], [0,31]]; + MULTIPLIER: TPointArray = [[1,1], [-1,1], [-1,-1], [1,-1]]; +var + b: TBox; + p, idx: TPoint; + color, pixel: Int32; + selected, inBounds: Boolean; + room: THouseRoom; + icon, rotated, tmp: TMufasaBitmap; +begin + Self.ReplaceColor($0, $1A1A1A); + + for b in Grid(HouseMap.AMOUNT, HouseMap.AMOUNT, HouseMap.SIZE-1, HouseMap.SIZE-1 ,[1,1], [0,0]) do + begin + selected := b.Contains(HouseMap.Selected.Map); + inBounds := HouseMap.HouseBounds.Contains([b.X1 div HouseMap.SIZE, b.Y1 div HouseMap.SIZE]); + + if selected then color := $00FFFF; + + for p in b.ToTPA().Connect() do + begin + pixel := Self.GetPixel(p); + if pixel = $EE then Continue; + if not selected and inBounds and (pixel <> HouseMap.Colors.Outdoors) then Continue; + + if not selected then + color := SRL.CalculateTransparency(pixel, $FFFFFF, 0.5, False); + Self.SetPixel(p.X, p.Y, color); + end; + end; + + icon.Init(); + icon.SetTransparentColor(0); + + for idx.Y := Max(HouseMap.HouseBounds.Y1, 0) to Min(HouseMap.HouseBounds.Y2, HouseMap.AMOUNT-1) do + for idx.X := Max(HouseMap.HouseBounds.X1, 0) to Min(HouseMap.HouseBounds.X2, HouseMap.AMOUNT-1) do + begin + room := HouseMap.ReadRoom(idx); + if room.Room = EHouseRoom.UNKNOWN then Continue; + p := [idx.X * HouseMap.SIZE, idx.Y * HouseMap.SIZE]; + tmp := HouseMap.GetIconBitmap(room.Room); + + rotated := tmp.RotateClockWise(room.Rotation); + tmp.Free(); + + Self.DrawBitmap(rotated, p); + rotated.Free(); + end; + + icon.Free(); +end; + + +procedure TScriptForm.POHObjectUpdate(sender: TObject); +var + i: TPoint; + combobox: TComboBox; + index: Int32; + room: THouseRoom; +begin + combobox := sender; + i := HouseMap.Selected.Matrix; + room := HouseMap.ReadRoom(i); + + index := combobox.getItemIndex(); + + case combobox.getName() of + 'garden_exit_portal_combobox': + begin + case index of + 0: room.Objects := []; + 1: room.Objects := [EHouseObject.EXIT]; + end; + end; + + 'repair_stand_combobox': + begin + case index of + 0: room.Objects := []; + 1: room.Objects := [EHouseObject.REPAIR_STAND]; + end; + end; + + 'servant_bag_combobox': + begin + case index of + 0: room.Objects := []; + 1: room.Objects := [EHouseObject.SERVANT_BAG]; + end; + end; + + 'trophy_combobox': + begin + case index of + 0: room.Objects := []; + else room.Objects := [EHouseObject.GLORY + index-1]; + end; + end; + + 'lectern_combobox': + begin + case index of + 0: room.Objects := []; + 1: room.Objects := [EHouseObject.LECTERN]; + end; + end; + + 'prayer_altar_combobox': + begin + case index of + 0: room.Objects := []; + 1: room.Objects := [EHouseObject.PRAYER_ALTAR]; + end; + end; + + 'left_portal_combobox': + begin + HouseMap.PortalChambers[i.Y, i.X].Left := EHousePortal(index); + end; + + 'middle_portal_combobox': + begin + HouseMap.PortalChambers[i.Y, i.X].Middle := EHousePortal(index); + end; + + 'right_portal_combobox': + begin + HouseMap.PortalChambers[i.Y, i.X].Right := EHousePortal(index); + end; + + 'digsite_combobox': + begin + case index of + 0: room.Objects -= [EHouseObject.DIGSITE_PENDANT]; + 1: if not (EHouseObject.DIGSITE_PENDANT in room.Objects) then + room.Objects += EHouseObject.DIGSITE_PENDANT; + end; + end; + + 'xeric_combobox': + begin + case index of + 0: room.Objects -= [EHouseObject.XERIC_TALISMAN]; + 1: if not (EHouseObject.XERIC_TALISMAN in room.Objects) then + room.Objects += EHouseObject.XERIC_TALISMAN; + end; + end; + + 'nexus_combobox': + begin + case index of + 0: room.Objects -= [EHouseObject.NEXUS]; + 1: if not (EHouseObject.NEXUS in room.Objects) then + room.Objects += EHouseObject.NEXUS; + end; + end; + + 'pool_combobox': + begin + room.Objects -= HousePools; + if index <> 0 then + room.Objects += EHouseObject.RESTORATION_POOL + index-1; + end; + + 'superior_garden_teleport_combobox': + begin + room.Objects -= SuperiorGardenTeleports; + if index <> 0 then + room.Objects += EHouseObject.SPIRIT_TREE + index-1; + end; + + 'jewellery_box_combobox': + begin + room.Objects -= JewelleryBoxes; + if index <> 0 then + room.Objects += EHouseObject.BASIC_JEWELLERY_BOX + index-1; + end; + + 'magic_altar_combobox': + begin + room.Objects -= MagicAltars; + if index <> 0 then + room.Objects += EHouseObject.ANCIENT_ALTAR + index-1; + end; + + 'cape_hanger_combobox': + begin + case index of + 0: room.Objects -= [EHouseObject.CAPE_HANGER]; + 1: if not (EHouseObject.CAPE_HANGER in room.Objects) then + room.Objects += EHouseObject.CAPE_HANGER; + end; + end; + end; + + HouseMap.WriteRoom(room, HouseMap.Selected.Matrix); +end; + +procedure TScriptForm.RefreshObjectsPanel(panel: TPanel; room: THouseRoom); +var + combobox: TLabeledComboBox; + teleports: TStringArray; + index: TPoint; +begin + panel.RemoveChildren(); + + case room.Room of + EHouseRoom.GARDEN, EHouseRoom.FORMAL_GARDEN: + begin + combobox.Create(panel, 'Exit portal:', 'Does this room have a exit portal?', [0,0],[166,50], True); + combobox.AddItemArray(['No', 'Yes']); + combobox.SetName('garden_exit_portal'); + combobox.SetItemIndex(SetCount(room.Objects)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + end; + + EHouseRoom.WORKSHOP: + begin + combobox.Create(panel, 'Repair armour stand:', 'Does this room have an armour stand?', [0,0],[166,50], True); + combobox.AddItemArray(['No', 'Yes']); + combobox.SetName('repair_stand'); + combobox.SetItemIndex(SetCount(room.Objects)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + end; + + EHouseRoom.BEDROOM: + begin + combobox.Create(panel, 'Servant money bag:', 'Does this room have a servant money bag?', [0,0],[166,50], True); + combobox.AddItemArray(['No', 'Yes']); + combobox.SetName('servant_bag'); + combobox.SetItemIndex(SetCount(room.Objects)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + end; + + EHouseRoom.QUEST_HALL: + begin + combobox.Create(panel, 'Trophy:', 'Does this room have a teleport trophy?', [0,0],[166,50], True); + combobox.AddItemArray(['None', 'Amulet of glory', 'Mythical cape']); + combobox.SetName('trophy'); + if SetCount(room.Objects) = 0 then combobox.SetItemIndex(0) + else if EHouseObject.GLORY in room.Objects then combobox.SetItemIndex(1) + else combobox.SetItemIndex(2); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + end; + + EHouseRoom.STUDY: + begin + combobox.Create(panel, 'Lectern:', 'Does this room have a lectern?', [0,0],[166,50], True); + combobox.AddItemArray(['No', 'Yes']); + combobox.SetName('lectern'); + combobox.SetItemIndex(SetCount(room.Objects)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + end; + + EHouseRoom.CHAPEL: + begin + combobox.Create(panel, 'Altar:', 'Does this room have an altar?', [0,0],[166,50], True); + combobox.AddItemArray(['No', 'Yes']); + combobox.SetName('prayer_altar'); + combobox.SetItemIndex(SetCount(room.Objects)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + end; + + EHouseRoom.PORTAL_CHAMBER: + begin + combobox.Create(panel, 'Left portal:', 'Does this room have a portal to the left of it''s entrance?', [0,0],[166,50], True); + teleports := [ + 'None', 'Arceuus Library', 'Draynor Manor', 'Battlefront', 'Varrock', + 'Grand Exchange', 'Mind Altar', 'Lumbridge', 'Falador ', + 'Salve Graveyard', 'Camelot', 'Seers'' Village', + 'Fenkenstrain''s Castle', 'Kourend Castle', 'East Ardougne', + 'Civitas illa Fortis', 'Watchtower', 'Yanille', 'Senntisten (Digsite)', + 'West Ardougne', 'Marim (Ape Atoll)', 'Harmony Island ', + 'Kharyrll (Canifis)', 'Moonclan', 'Cemetery', 'Waterbirth Island', + 'Barrows', 'Carrallanger (Graveyard of Shadows)', 'Fishing Guild', + 'Catherby', 'Annakarl (Demonic Ruins)', 'Ape Atoll Dungeon', + 'Ghorrock (Frozen Waste Plateau)', 'Troll Stronghold', 'Weiss' + ]; + index := HouseMap.Selected.Matrix; + combobox.AddItemArray(teleports); + + combobox.SetName('left_portal'); + combobox.SetItemIndex(Ord(HouseMap.PortalChambers[index.Y, index.X].Left)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + + combobox := []; + combobox.Create(panel, 'Middle portal:', 'Does this room have a portal in front of it''s entrance?', [0,50],[166,50], True); + combobox.AddItemArray(teleports); + combobox.SetName('middle_portal'); + combobox.SetItemIndex(Ord(HouseMap.PortalChambers[index.Y, index.X].Middle)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + + combobox := []; + combobox.Create(panel, 'Right portal:', 'Does this room have a portal to the right of it''s entrance?', [0,100],[166,50], True); + combobox.AddItemArray(teleports); + combobox.SetName('right_portal'); + combobox.SetItemIndex(Ord(HouseMap.PortalChambers[index.Y, index.X].Right)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + end; + + EHouseRoom.PORTAL_NEXUS: + begin + combobox.Create(panel, 'Digsite pendant:', 'Does this room have a digsite pendant?', [0,0],[166,50], True); + combobox.AddItemArray(['No', 'Yes']); + combobox.SetName('digsite'); + combobox.SetItemIndex(Int32(EHouseObject.DIGSITE_PENDANT in room.Objects)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + + combobox := []; + combobox.Create(panel, 'Xeric''s talisman:', 'Does this room have a xeric''s talisman?', [0,50],[166,50], True); + combobox.AddItemArray(['No', 'Yes']); + combobox.SetName('xeric'); + combobox.SetItemIndex(Int32(EHouseObject.XERIC_TALISMAN in room.Objects)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + + combobox := []; + combobox.Create(panel, 'Portal nexus:', 'Does this room have a portal nexus?', [0,100],[166,200], True); + combobox.AddItemArray(['No', 'Yes']); + combobox.SetName('nexus'); + combobox.SetItemIndex(Int32(EHouseObject.NEXUS in room.Objects)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + end; + + EHouseRoom.SUPERIOR_GARDEN: + begin + combobox.Create(panel, 'Pool:', 'Does this room have a pool?', [0,0],[166,50], True); + combobox.AddItemArray(['None', 'Restoration', 'Revitalisation', 'Rejuvenation', 'Fancy rejuvenation', 'Ornate rejuvenation']); + combobox.SetName('pool'); + combobox.SetItemIndex(0); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + + combobox := []; + combobox.Create(panel, 'Teleport:', 'Does this room have a teleport?', [0,50],[166,50], True); + combobox.AddItemArray(['None', 'Spirit tree', 'Obelisk', 'Fairy ring', 'Spirit fairy tree']); + combobox.SetName('superior_garden_teleport'); + combobox.SetItemIndex(0); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + end; + + EHouseRoom.ACHIEVEMENT_GALLERY: + begin + combobox.Create(panel, 'Jewellery box:', 'Does this room have a jewellery box?', [0,0],[166,50], True); + combobox.AddItemArray(['None', 'Basic', 'Fancy', 'Ornate']); + combobox.SetName('jewellery_box'); + + if EHouseObject.BASIC_JEWELLERY_BOX in room.Objects then + combobox.SetItemIndex(1) + else if EHouseObject.FANCY_JEWELLERY_BOX in room.Objects then + combobox.SetItemIndex(2) + else if EHouseObject.ORNATE_JEWELLERY_BOX in room.Objects then + combobox.SetItemIndex(3) + else + combobox.SetItemIndex(0); + + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + + combobox := []; + combobox.Create(panel, 'Altar:', 'Does this room have an altar?', [0,50],[166,50], True); + combobox.AddItemArray(['None', 'Ancient', 'Lunar', 'Dark', 'Occult']); + combobox.SetName('magic_altar'); + if EHouseObject.ANCIENT_ALTAR in room.Objects then + combobox.SetItemIndex(1) + else if EHouseObject.LUNAR_ALTAR in room.Objects then + combobox.SetItemIndex(2) + else if EHouseObject.DARK_ALTAR in room.Objects then + combobox.SetItemIndex(3) + else if EHouseObject.OCCULT_ALTAR in room.Objects then + combobox.SetItemIndex(4) + else + combobox.SetItemIndex(0); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + + combobox := []; + combobox.Create(panel, 'Cape hanger:', 'Does this room have a cape hanger?', [0,50],[166,50], True); + combobox.AddItemArray(['None', 'Yes']); + combobox.SetName('cape_hanger'); + combobox.SetItemIndex(Int32(EHouseObject.CAPE_HANGER in room.Objects)); + combobox.ComboBox.setOnChange(@Self.POHObjectUpdate); + end; + end; +end; + + +procedure TScriptForm.POHMouseDown(sender: TObject; btn: TMouseButton; {$H-}shift: TShiftState;{$H+} x, y: Integer); +var + tmp: TMufasaBitmap; + parent: TControl; + combobox: TComboBox; +begin + if btn <> TMouseButton.mbLeft then Exit; + HouseMap.Selected.Map := [x, y]; + HouseMap.Selected.Matrix := [x div HouseMap.SIZE, y div HouseMap.SIZE]; + + parent := TSimbaImageBox(sender).getParent().getParent(); + combobox := parent.GetChild('room_combobox'); + + combobox.setItemIndex(Ord(HouseMap.ReadRoom(HouseMap.Selected.Matrix).Room)); + + tmp := HouseMap.Map.Copy(); + tmp._DrawPOH(); + TSimbaImageBox(sender).getBackground().LoadFromMufasaBitmap(tmp); + TSimbaImageBox(sender).Update(); + tmp.Free(); + + Self.RefreshObjectsPanel(parent.GetChild('objects_panel'), HouseMap.ReadRoom(HouseMap.Selected.Matrix)); +end; + +procedure TScriptForm.POHDecorationUpdate(sender: TObject); +var + parent: TControl; + combobox: TComboBox; + imgbox: TSimbaImageBox; + tmp: TMufasaBitmap; +begin + combobox := sender; + parent := combobox.getParent().getParent().getParent(); + imgbox := parent.GetChild('poh_builder'); + + if HouseMap.Map = nil then + begin + HouseMap.Init(32, 13); + combobox.setItemIndex(Ord(HouseMap.Decoration)); + end + else + begin + HouseMap.SetColors(EHouseDecoration(combobox.getItemIndex())); + HouseMap.Redraw(); + end; + + tmp := HouseMap.Map.Copy(); + tmp._DrawPOH(); + imgbox.getBackground().LoadFromMufasaBitmap(tmp); + imgbox.Update(); + tmp.Free(); + HouseMap.Config.Put('layout', HouseMap.ToEncodedString()); +end; + + +procedure TScriptForm.RoomUpdate(sender: TObject); +var + parent: TControl; + imgbox: TSimbaImageBox; + tmp: TMufasaBitmap; + room: EHouseRoom; + old, new: THouseRoom; +begin + parent := TComboBox(sender).getParent().getParent().getParent(); + imgbox := parent.GetChild('poh_builder'); + + room := EHouseRoom(TComboBox(sender).getItemIndex()); + old := HouseMap.ReadRoom(HouseMap.Selected.Matrix); + if old.Room = room then new := old + else new := [room, 0, []]; + HouseMap.DrawMap(new, HouseMap.Selected.Matrix); + + tmp := HouseMap.Map.Copy(); + tmp._DrawPOH(); + imgbox.getBackground().LoadFromMufasaBitmap(tmp); + imgbox.Update(); + tmp.Free(); + + Self.RefreshObjectsPanel(parent.GetChild('objects_panel'), new); +end; + +procedure TScriptForm.OnRotateRoom(sender: TObject); +var + parent: TControl; + imgbox: TSimbaImageBox; + current: THouseRoom; + tmp: TMufasaBitmap; +begin + parent := TComboBox(sender).getParent().getParent(); + imgbox := parent.GetChild('poh_builder'); + current := HouseMap.ReadRoom(HouseMap.Selected.Matrix); + + case TButton(sender).getName() of + 'rotate_left_button': current.Rotation -= 1; + 'rotate_right_button': current.Rotation += 1; + end; + + case current.Rotation of + -1: current.Rotation := 3; + 4: current.Rotation := 0; + end; + + HouseMap.DrawMap(current, HouseMap.Selected.Matrix); + + tmp := HouseMap.Map.Copy(); + tmp._DrawPOH(); + imgbox.getBackground().LoadFromMufasaBitmap(tmp); + imgbox.Update(); + tmp.Free(); +end; + +procedure TScriptForm.OnShow(sender: TObject); override; +var + imgbox: TSimbaImageBox; +begin + inherited; + imgbox := TForm(sender).GetChild('poh_builder'); + if imgbox <> nil then + begin + imgbox.BackgroundChanged(); + imgbox.setZoom(2); + imgbox.MoveTo(130, 200); + end; +end; +{%codetools on} + +function TScriptForm.CreateHouseBuilder(): TTabSheet; +var + panel: TPanel; + imgbox: TSimbaImageBox; + decoration, room: TLabeledComboBox; + rotateLeft, rotateRight: TButton; + caption: TLabel; +begin + Self.AddTab('House Builder'); + Result := Self.Tabs[High(Self.Tabs)]; + + panel.Create(Result); + panel.setAlign(TAlign.alLeft); + panel.setBevelWidth(0); + + decoration.Create(panel, 'Decoration:', 'Change your house decoration', [0, 10], [166,0], True); + decoration.AddItemArray(['Basic wood', 'Basic stone', 'Whitewashed stone', 'Fremennik-style wood', 'Tropical wood', 'Fancy stone', 'Deathly mansion', 'Twisted theme', 'Hosidius house', 'Cosy cabin']); + decoration.SetItemIndex(0); + decoration.ComboBox.setOnChange(@Self.POHDecorationUpdate); + + room.Create(panel, 'Room:', 'Change the selected room', [0, 50], [166,0], True); + room.SetName('room'); + room.AddItemArray([ + 'None', 'Parlour', 'Garden', 'Kitchen', 'Dining room', 'Workshop', 'Bedroom', + 'Skill hall', 'League hall', 'Games room', 'Combat room', 'Quest hall', + 'Menagerie (outdoors)', 'Menagerie (indoors)', 'Study', 'Costume room', + 'Chapel', 'Portal chamber', 'Formal garden', 'Throne room', + 'Superior garden', 'Portal nexus', 'Achievement gallery' + ]); + room.ComboBox.setOnChange(@Self.RoomUpdate); + + rotateLeft.Create(panel, 'Rotate Left', 'Rotate the current room counter clockwise', [0,100], [0,0], True); + rotateLeft.setName('rotate_left_button'); + rotateLeft.setOnClick(@Self.OnRotateRoom); + rotateRight.Create(panel, 'Rotate Right', 'Rotate the current room clockwise', [92,100], [0,0], True); + rotateRight.setName('rotate_right_button'); + rotateRight.setOnClick(@Self.OnRotateRoom); + + panel.Create(panel, [0, 150], [0,300], True); + panel.SetName('objects_panel'); + panel.setCaption(''); + panel.setBevelWidth(0); + + panel.Create(Result); + panel.setAlign(TAlign.alClient); + panel.setBevelWidth(0); + + caption.Create(panel,'LEFT CLICK to select a room, RIGHT CLICK to move. CTRL + WHEEL to zoom.', '', [0,0], [0,0], True); + caption.setAlign(TAlign.alTop); + + imgbox.Create(panel); + imgbox.setName('poh_builder'); + imgbox.setOnPaintArea(@Self.HousePaintArea); + imgbox.setOnMouseDown(@Self.POHMouseDown); + Self.POHDecorationUpdate(decoration.ComboBox); + room.SetItemIndex(Ord(HouseMap.ReadRoom(HouseMap.Selected.Matrix).Room)); + Self.RoomUpdate(room.ComboBox); +end; + diff --git a/osr/walker/house.png b/osr/walker/house.png index 1abf8abbac4a34a835501cb76561be4b660c4339..f5ee1da3046ef39b86fc7dc53f2aa69eff2f9fef 100644 GIT binary patch literal 1282 zcmeAS@N?(olHy`uVBq!ia0y~yV0r*#D{wFY$vdZ`4g)F90*}aI1_nK45N51cYG1~{ zz_P{D#WAFU@$H=4qQwRRE^pVyG~D^$^N=HM=bo*SIi@@do*k}R`A6W>imRUm&K+Tj z&)*Yxn-R)qi7^%D4YtwXr%~>3c}eEQMFA zXWUqSF0iNb?TYos(vlXXb<5Wh ze`v`!_3et4B}RrtOrNV976pbzWq}0_mWLt_FatS^91aXj%uFI0%rY`S7Fb0A3y|+_ z!Ce7X4pfog(7=@1(98@{3sTYNkkGxnqHlt_oWci7mMg_yAG3tny)n}}w;Ck-Oz4BS z##6}`OZ_E%)ssc*ee&l%`q;2zs{cu*+O3oBHSGVP*BFsIrAk#UEb+vwV|)MAc5j}Q z{@Krbe$tKh7rfW%KMVg-*>Uh^?W~P&7vA1q@jr7aOSH{ek44O2#|kt!7z5pRHb995 z#@7b&QRV+t2h56G%boh|`fFw3y+z4(hGwEcW*_8kA6WUq)$y9G)Asu9|CAOTms0sQ zWB#;vmlMrczdzb@XTNXFr++in+@8LpK63Z_hZQroryjq#{{A$bxGiQC^%kc8#S@ml zwy13^vU()Zc7ToL*}tpt5eIW7a0UZIX1-U0?mU0n&rf(E?y@jov~#rlaPpuOi`}FH zua8?6+Y3k?e|AlN{@idyhDH@et|=-Shk)_%mg7j*ldbb2-XC7O|DGZjFbtz@)sMc`T08wl?44Onp??}rS8+xh?f$u*Bob8L%LyYxYTK(gDhUxUZ6Z zciGl1`yY|ZH*|^rf6*V$^8U1XF>{XEvE3~u|EFyKINADFmT~ReLofd96kq%3=ppGB zyYrnlDrr4R|2{wZMEBx9tf!K)d0H4)!~{3otK40mG7mDd!`HK?yAV zZjc8C+}|6<#<0}e#K79c;A{@#{uW%|aI@dm7^Yrug>eMmKYQp@4sEY!7|6}k+BG``wMo7sn2fyJTfIj@6oI-=hW!TRg6jN z?ykH4BKWD2&GChs)|y;x*jc>u>tcV?>oMm8jkjHYv3g@*y|4A;#h*5$y^%U!xaQLW n8(^`qwj`-1YxPc{S@nzz+4GhjbP7=d7HSNhu6{1-oD!M<;bS%W literal 1648 zcmdUwe@s(X6vuC2!cw3t3c@H%HYKpxMyE?vAbWLCN-3i-wg8&Z`D0^C#+b_FN1?CC z&}mg%sc6hb31AzXfIwqyTzy-#8-(eDmIWtBl@y_(Vf{@@-+OzHKlbNlmi@8)b93*x z_kPdGIp=fl@%%h~1U-%p0EiG|XTAeK&;xRw_(Bjlp4@#@20+L*LFUePtG{{rt?+dG zj__6W#7$51qsEex;gj4C3m%V3qWg#5j(M#ys$21VEN(B_E^hYOBN53K%iyL+*x|zt zStM1n-y>)Ta+VW#r+w!!qC#(q@QM3`00ny?yjY>zJ_jaXM z$j=?IPkA!(?BZChG0l3R&zjYLmG;!Z@DI+T9gy%CRQ`I>wiBoQW2z1#VOfYLw-$KH z!!@B!)HH^CeQ{86189xPsg%^XVwjGVxXNt5YuIXQ)>r}LFVrqc8p<{ktOSf%lDg3r z#@(Gee5#|f!L1op@!(bld~a{gXHg06PKYN*VyZ*_o1(Mk0OExB=?`J1{?SH#LFITe z9>vB>AH4VrB;*rWt#@*2gGbjE9_*~H-E%F1O2Q|0n8U4E>=X5rF7LldQ(irLq zH??AXIAkSDv4E$RCK+TO%U{(OXg}rpx6Y%cnMKg_@|Yx7O0tcC`o zx7dIWqr*h`JgR_k0m4)!di?pdTy_e1c1#v zLseS4gw)FJM%#Mjl|R!eC+51-pIlnES7yp5Nm`*%js!<3T|pfyd=1cSWYL9`EhIFh zk8nRnanyvfD4{g&PQj+c6}tWuPvh?ST;}kLMWYm&|3FX-Rs_XiWV2A$M>M{^P_-Fd zgX`?$bw$&j0i85&5`SW*0w&R?ee}Oa|6h8f#LYV}3-`@^;+H%G|7@S3stE61{LNje zmU?`JmJEZJL%957gd|X9JUdgv>8(c^(R8oj+^(1t*B{S0N%I#>C1I=0?$#;2)1L3y erSzYi6F@(;EP)7G_m}r;*AW=e0Z2o-B{kp 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