Co tam, panie, w WoW-ie słychać?

2008-07-06 0:27

WoWScrnShot_101807_213347.jpgJakiś tydzień temu naszła mnie ochota, by zagrać sobie znowu w “jedynie słusznego MMORPG-a”, czyli sławetny World of Warcraft. Ciężko powiedzieć, dlaczego, skoro przez prawie 10 miesięcy nie chciało mi się tykać tej gry nawet końcem małego palca… Pewnie dobrą wymówką będą wakacje – krótkie wprawdzie, ale jednak :)
Wnioski z ponownego odwiedzenia wirtualnego świata WoW są, ogólnie mówiąc, różne i ciekawe zarazem. Najważniejszą obserwacją jest chyba to, co ktoś nazwał coraz większym “zcasualowaniem się” tej gry, a ja określam mianem “syndromu zbliżającego się dodatku”. (Drugi expansion pack do tej gry wejdzie właśnie niedługo w fazę beta-testów, na które można się już zapisywać). W skrócie polega to na tym, iż wszystkie elementy gry określane zgrabnie jako endgame – czyli te najtrudniejsze i najbardziej wymagające – są teraz znacznie bardziej dostępne nie tylko dla 5 czy 10 procent hardcore‘owych graczy. Lokacje, które kiedyś wymagały dobrze skoordynowanej drużyny 10 lub 25 osób (raidy), najlepiej z jednej gildii, są teraz w zasięgu grup formowanych spontanicznie z graczy w gruncie rzeczy mniej lub bardziej przypadkowych.

WoWScrnShot_063008_101143.jpgJest to w sumie naturalna kolej rzeczy, że dodawany na bieżąco przez twórców gry content z czasem się dewaluuje, co skutkuje wprowadzeniem nowych lokacji, bossów, itp. – i tak to się zasadniczo kręci :) Wielkim problemem chyba wszystkich MMORPG-ów jest jednak schematyczność, bowiem po pewnym czasie (czasem dłuższym – zwłaszcza w przypadku tak dopracowanej gry jak WoW) stwierdzić można, że to wszystko tak naprawdę już było. Trochę inaczej, ale duże analogie są jednak widoczne.
WoWScrnShot_062908_233755.jpgRecykling pomysłów jest zauważalny zwłaszcza w przypadku walk z bossami, co jest esencją każdego raidu. Dla niewtajemniczonych wyjaśniam, że podstawowy schemat przebiegu takiej walki w klasycznym MMORPG-u typu fantasy wyróżnia trzy elementy, czyli role pełnione przez poszczególnych graczy:

  • Rola tanka, pełniona przez postać (postaci) w ciężkiej zbroi z dużą ilością życia, czyli klasy wojownika, paladyna lub podobnych. Jej (ich) zadaniem jest skupić na sobie uwagę bossa i przyjąć większość zadawanych przez niego obrażeń.
  • Rola healera, czyli jednej lub kilku osób utrzymujących przy życiu cała grupę, ze szczególnym uwzględnieniem tanków. Zwykle jest to zadanie dla postaci posługujących się jakąś formą magii.
  • Rola damage dealera (“zadawacza obrażeń”), czyli postaci mających za zadanie zrobić złemu bossowi odpowiednio dużą krzywdę :) Wachlarz sposobów jest tutaj szeroki i obejmuje zarówno magię, jak i walkę fizyczną wręcz czy strzelanie z dystansu.

Mało skomplikowane, prawda? W czystej wersji (zwanej wyjątkowo obrazowo tank & spank) byłaby to w istocie wielka nuda. Dlatego zawsze występują liczne dodatkowe atrakcje, których może być niewiele lub całkiem sporo, ale według mnie wpadają zawsze w jedną z czterech poniższych grup:

  • Niebezpieczeństwa wymagające od graczy poruszania się albo przynajmniej odpowiedniego ustawienia. Zazwyczaj oznacza to konieczność stania w odpowiednim odstępie od innych, gdyż inaczej może wydarzyć się coś niedobrego. W bardziej skomplikowanym przypadku może to być konieczna okresowa zmiana pozycji, oznaczająca czasem długi bieg, wymagająca szybkiego refleksu, uwzględnienia kwestii widoczności lub poruszania się w trzech wymiarach. Niekiedy pomyłka kończy się bardzo nieprzyjemnie :)
  • Dodatkowi wrogowie, towarzyszący głównemu bossowi, z którymi trzeba sobie jakoś poradzić. Bywa, że problemem jest głównie ich ilość; niekiedy też są znacznie większym kłopotem od samego bossa. I wreszcie: wrogowie ci mogą mieć również całkiem dużo wspólnego z samymi graczami…
  • Innowacje w dziedzinie tankowania. Niestety (a może raczej -stety) bossowie generalnie nie wykazują się inteligencją: jako byty całkowicie oskryptowane nie koncentrują się na szybkim wyeliminowaniu kluczowych postaci, czyli healerów, lecz z uporem biją w ciągle leczone postaci ciężkozbrojne. Cóż, takie życie ;) Tym niemniej bywa, że czasami i tutaj zdarzają się jakieś innowacje, co objawia się na przykład koniecznością rotacji tanków, pełnienia tej roli przez klasy zupełnie nietypowe lub nawet przez postacie nie będące graczami (non-player characters – NPCs). Wyjątkowo boss może w ogóle nie potrzebować tankowania lub zwyczajnie być na nie odporny.
  • Łamanie schematów dotyczących ról poszczególnych typów postaci w całej walce. Healerzy mogą na przykład niemal całkowicie stracić możliwość leczenia na ten czas, co zmusza ich do zadawania obrażeń. W innym przypadku część graczy zwykle zajęta tym właśnie zadaniem musi dla odmiany zająć uwagę dodatkowych przeciwników biorących udział w walce. A bywa i tak, że tradycyjne klasy postaci zupełnie przestają mieć znaczenie, choć taka oryginalność jest wielce rzadka.

WoWScrnShot_070308_223122.jpgJaki jest wniosek z tej nieco przydługiej wyliczanki? Ano taki, że w WoW-ie nowatorskie pomysły pojawiają się niestety coraz rzadziej. Spośród 17 przykładów, które mimochodem wymieniłem (tak, chodzi o te dziwne linki ;D), tylko cztery pochodzą z wydanego półtora roku temu pierwszego dodatku, The Burning Crusade. Wszystkie inne koncepty pojawiły się już w podstawowej wersji gry i są tylko użytkowane ponownie z niewielkimi zmianami.
Czy to oznacza niedaleki zmierzch jej popularności? To niewykluczone. Ale przecież zawsze pozostaje jeszcze rozgrywka typu Player vs Player. Tam tendencje są zgoła odwrotne – upraszczające, a najlepszych ich wyrazem są areny. Weźmy po prostu dwie drużyny składające się z 2, 3 lub 5 graczy i niech wygra lepsza. Mało oryginalne, ale zadziwiająco popularne :D

Tags:
Author: Xion, posted under Games, Thoughts » Add comment

Triki z PowerShellem #6 – Mail z załącznikami

2008-07-03 20:05

Kiedy mamy komuś przesłać plik, możemy niekiedy użyć do tego zwykłej poczty e-mail. Nie jest to oczywiście możliwe zawsze: plik nie powinien być zbyt duży i zwykle nie może też należeć do żadnego z "niebezpiecznych" typów (np. aplikacji EXE), by serwery pocztowe mogły go przepchnąć bez narażania lub zatykania sieci.
Jeśli w naszym przypadku tak jest, to możemy wykonać całą operację na przykład przy pomocy poniższego skryptu PowerShella:

# MailFile.ps1 - Wysyła podany plik e-mailem na podany adres
# Parametr: nazwa pliku lokalnego
param([string]$file = $(throw "File not specified"))

# Stałe
$SERVER = "moj.server.pl"
$LOGIN = "loginSmtp"
$PASSWORD = "hasłoSmtp"
$FROM_ADDRESS = "ja@server.pl"

# Odczytanie adresu docelowego
$recipentAddress = Read-Host -Prompt "Recipent e-mail address"
$recipent = New-Object Net.Mail.MailAddress @($recipentAddress)

# Złożenie maila
$mail = New-Object Net.Mail.MailMessage
$mail.From = New-Object Net.Mail.MailAddress @($FROM_ADDRESS)
$mail.To.Add($recipent)
$mail.Subject = (New-Object IO.FileInfo @($file)).Name

# Dodanie załącznika
$attachment = New-Object Net.Mail.Attachment @($file,
    [Net.Mime.MediaTypeNames+Application]::Octet)
$cd = $attachment.ContentDisposition
$cd.CreationDate = [IO.File]::GetCreationTime($file)
$cd.ModificationDate = [IO.File]::GetLastWriteTime($file)
$cd.ReadDate = [IO.File]::GetLastAccessTime($file)
$mail.Attachments.Add($attachment)

# Łączenie z serwerem SMTP i wysłanie maila
$smtp = New-Object Net.Mail.SmtpClient @($SERVER)
$smtp.Credentials = New-Object Net.NetworkCredential @($LOGIN, $PASSWORD)
# $smtp.EnableSsl = $true # Odkomentowujemy, jeśli serwer wymaga SSL
$smtp.Send($mail)

Jak widać, nie jest to nic skomplikowanego. A jak wykorzystać go w praktyce?... Podobnie jak prezentowany wcześniej skrypt do uploadu na serwer FTP, można go wywołać komendą w rodzaju:

powershell -Command . 'C:\Sciezka\Do\Skryptu\MailFile.ps1' '%1'

którą podpinamy do menu kontekstowego plików lub podmenu Wyślij do.
W ten prosty sposób możemy oszczędzić sobie uruchamiania całego klienta poczty, względnie przeglądarki z webmailem. Naturalnie sporo rzeczy - jak chociażby temat wysyłanej wiadomości czy jej treść - możemy w zaprezentowanym kawałku kodu zmienić lub uzupełnić.

Tags: , ,
Author: Xion, posted under Applications, Internet » 1 comment

Wyliczenia kwalifikowane

2008-07-01 23:26

W C++ typy wyliczeniowe deklaruje się zwykle poprzez coś podobnego do poniższego kawałka kodu:

enum Sides { Left = -1, Middle = 0, Right = 1 };

Jego skutkiem jest jednak to, że nazwy stałych typu (tutaj: Left, Middle i Right) będą widoczne w całej przestrzeni nazw zawierającej daną deklarację enum. Jeśli więc przypadkiem jest ona globalna, to całkiem łatwo może ona spowodować konflikt chociażby z innym typem w rodzaju:

enum Keys { Left, Right, Up, Down, /* ... */ };

Aby zapobiegać takim sytuacjom, w Javie i C# stałe wyliczeniowe muszą być kwalifikowane nazwą odpowiedniego typu - używa się więc Sides.Left i Keys.Left. W C++ jest jednak inaczej, gdyż blok enum sam w sobie nie tworzy zasięgu (w przeciwieństwie np. do bloków class).

Można temu częściowo zaradzić w następujący sposób:

struct Sides
{
    enum _Enum { Left = -1, Middle = 0, Right = 1 };
};
typedef Sides::_Enum Side;

dzięki czemu możemy z naszego enuma korzystać tak:

Side foo;
foo = Sides::Left// OK
foo = 1; // błąd - nie można przypisać liczby
foo = Right; // błąd - Right nie jest w przestrzeni globalnej

Różnica względem wspomnianych dwóch języków polega na tym, że nazwa typu wyliczeniowego (Side) oraz kwalifikator stałych (Sides) nie są takie same. Wydaje się jednak (przynajmniej mi się tak wydaje :]), że w tym przypadku takie rozróżnienie jest logicznie poprawne i wygląda nawet czytelniej niż gdyby obie nazwy były identyczne.
Trik ten można naturalnie opakować w makro, które umożliwi łatwe tworzenie typów wyliczeniowych z kwalifikowanymi nazwami stałych. Nie poprawi to oczywiście funkcjonalności enumów w C++, ale przynajmniej sprawi, że będą ładniej wyglądały :D

Tags: , ,
Author: Xion, posted under Programming » 10 comments

Konstruktory bezparametrowe

2008-06-28 12:12

W C++ konstruktor domyślny jest generowany automatycznie, jeśli w klasie nie zostanie zdefiniowany żaden inny. W przeciwnym wypadku nie jst on tworzony, co może prowadzić do sytuacji, gdy klasa posiada jedynie takie konstruktory, które wymagają podania parametrów. Jest ona niekorzystna przynajmniej z dwóch powodów:

  1. Gdy dziedziczymy po takiej klasie, musimy jakoś zapewnić, że w klasie pochodnej zostanie wywołany konstruktor klasy bazowej. Zwykle robi się to za pomocą listy inicjalizacyjnej:
    Derived(int param1, int param2) : Base(param1, param2) { /*... */ }

    Kiedy jednak zmieni się postać konstruktora klasy bazowej, wówczas modyfikacja będzie musiała dotyczyć wszystkich bezpośrednich potomków tej klasy. W nowej wersji standardu C++ ma być aczkolwiek wprowadzone nowe zadanie dla słowa kluczowego using, które pozwoli na automatyczne utworzenie konstruktorów "przekaźnikowych" - takich, których jedyną rolą jest wywołanie konstuktorów bazowych z takimi samymi parametrami jak te otrzymane w konstruktorze pochodnym. Jeśli jednak przy okazji chcemy coś zmienić czy pominąć, to nie ma rady: trzeba całą tę "sztafetę" zakodować ręcznie.

  2. Jeszcze gorzej jest, kiedy klasa bez konstruktora domyślnego jest wirtualną klasą bazową. Wówczas o jej prawidłowej inicjalizacji muszą pamiętać nie tylko klasy bezpośrednio pochodne, ale w ogóle wszystkie potomne! Dokładnie tak: nawet w piątym czy dziesiątym pokoleniu hierarchii o korzeniu w wirtualnej klasie bazowej, klasy pochodne muszą w swoich konstruktorach wywoływać konstruktory owego korzenia. To oczywiście sprawia, że każda zmiana musi być rozpropagowana na całe to drzewo dziedziczenia (a właściwie graf, bo pewnie zawiera on cykle :>).

Dlatego też klasy będące przede wszystkim klasami bazowymi w dziedziczeniu dobrze jest wyposażać w konstruktory bezparametrowe. Może to aczkolwiek wymagać wyróżnienia w obiekcie stanów Zainicjowany-Niezainicjowany, które powinny być sprawdzane w jego metodach i w razie potrzeby odpowiednio sygnalizowane.

Tags: ,
Author: Xion, posted under Programming » 5 comments

Bo to zła instrukcja była…

2008-06-24 21:56

Ostatnio na forum Warsztatu wyrosła dyskusja wokół niepozornej instrukcji break. Zaczęło się od zasłyszanej opinii, że korzystanie z niej jest objawem złego stylu programowania i że jest generalnie niezalecane. Argumentem na rzecz takiego twierdzenia jest to, że break jest podobny w swoim działaniu do etykiet i "wyklętej" instrukcji goto:

while (WarunekPetli())
{
    if (CosSieStalo()) goto ZaPetla;   // prawie jak break
}
ZaPetla:
    // ...

W sumie wyszła z tego całkiem długa polemika, która ujawniła kilka ciekawych faktów. Po pierwsze: przejrzystość kodu jest w całkiem dużym stopniu kwestią subiektywną i o ile jednemu może przeszkadzać brak (lub niewystarczająca ilość) komentarzy, to dla drugiego o wiele ważniejsze może być używanie lub nieużywanie określonych konstrukcji językowych. Po drugie: oprócz wspomnianego już goto czy wywołanego we wspomnianym wątku breaka, do instrukcji potencjalnie niepożądanych swobodnie można zaliczyć bardzo wiele innych rozwiązań - zależnie od upodobań, poczucia 'słuszności', bieżącej fazy księżyca i pewnie mnóstwa jeszcze innych kryteriów. I tak dostało się chociażby pętlom "nieskończonym" (typu while(true) lub for(;;)), wspomniano coś o wyjątkach, a sam napomknąłem o instrukcji continue lub "przedwczesnym" return:

void Funkcja()
{
    if (/* warunek wyjścia */) return;
    // reszta instrukcji
}

Teoretycznie można by wszystkie te konstrukcje usunąć w imię ideologicznej czystości programowania strukturalnego, w którym każdy blok ma dokładnie jedno wejście i wyjście. Ale tu na szczęście wkracza wniosek trzeci: języki programowania używane w praktyce nie są żadnymi doskonałymi modelami, nad którymi należy się zachwycać. To narzędzia i jako takie powinny być przede wszystkim wygodne w użytkowaniu.
Więc nie warto drzeć kotów o sens stosowania break czy nawet goto (która to instrukcja też ma swoje zastosowania) i wydawać ogólne sądy na temat ich 'nieprofesjonalności'. Bo nawet jeśli ta czy inna instrukcja rzekomo jest zła, to korzystający z niej kod nadal może być dobry, czytelny i łatwy do modyfikacji... Łącznie z refaktoringiem, który tego czy innego breaka może przecież usunąć ;P

Tags: ,
Author: Xion, posted under Programming, Thoughts » 3 comments

Znaki białe i bielsze

2008-06-22 22:23

W językach programowania i opisu (np. HTML lub XML) ważną rolę odgrywają tzw. białe znaki (whitespaces). Nie są one nigdy wypisywane ani drukowane jako symbole, ale zajmują miejsce na ekranie i czasem powodują dodatkowe efekty (jak na przykład przejście do nowego wiersza w przypadku znaku końca linii - line feed). W wielu przypadkach parsery tekstowe ignorują większą ilość tego typu znaków, pozwalając dzięki temu na dowolne formatowanie kodu. Ale czasami jest wręcz odwrotnie: istnieje mianowicie ezoteryczny język programowania - zwany oryginalnie Whitespace - w którym to właśnie znaki niedrukowane (a dokładniej spacja, tabulator i znak końca wiersza) są jedynymi, które są interpretowane jako kod.

Jakie jednak znaki należy uważać za niedrukowane?... Sprawa nie jest taka prosta, gdyż, podobnie jak rozróżnienie liter wielkich i małych, zależy ona od ustawień lokalnych języka. Większość języków programowania dysponuje aczkolwiek odpowiednią metodą sprawdzenia. W C++ jest to na przykład standardowa funkcja isspace. Ponieważ jednak w grę wchodzą ustawienia kulturowe, rezultaty zależą od języka systemu operacyjnego (przynajmniej teoretycznie).
W Unicode mamy na szczęście standardowo zdefiniowane, które znaki są traktowane jako białe. Te właśnie są sprawdzane przez metody w rodzaju java.lang.Character.isWhitepace w Javie czy System.Char.IsWhiteSpace w .NET. Działają one tak samo niezależnie od kontekstu kulturowego.

A co mamy zrobić, jeśli optujemy za ANSI? Wówczas możemy ustalić sobie swój własny zbiór znaków interpretowanych jako białe. Według mnie najlepszym zestawem jest następujący:

bool IsCharWhitespace(char c)
{
    return (c == 0x09 || c == 0x0A || c == 0x0D || c == 0x20);
}

Te znaki to kolejno: tabulator (poziomy) (\t), line feed (\n), znak powrotu karetki (carriage return, \r) i zwykła spacja. Za takim wyborem przemawia chociażby fakt, że właśnie te znaki są uważane w XML za białe i że obejmują różne standardy kodowania końca wiersza na różnych platformach. Dwa inne znaki: tabulator pionowy (0x0B) i form feed (0x0C) są jeszcze uważane za białe w C, ale stosuje się je chyba zbyt rzadko, aby trzeba się było nimi przejmować :)

Tags: ,
Author: Xion, posted under Programming » Add comment

Triki z PowerShellem #5 – Skróty

2008-06-20 13:53

Pisząc skrypty w PowerShellu mamy dostęp do niemal całej platformy .NET, zawierającej grubo ponad dziesięć tysięcy klas, dlatego nieczęsto będziemy potrzebowali czegoś spoza tego wielkiego bogactwa. Ale takie przypadki też istnieją. Na szczęście PSh umożliwia też dostęp do innych narzędzi niż .NET - jak choćby do starych (nie)dobrych obiektów COM.
Przy ich pomocy można zrobić całkiem sporo, zwłaszcza jeśli chodzi o sterowanie aplikacjami, które udostępniają na zewnątrz odpowiednie interfejsy (a robi tak wiele większych programów, nie tylko Microsoftu). Ciekawsze jest chyba jednak łączenie technologii: "starego" COM-a i nowego .NET-a, aby razem zrobić coś użytecznego. Oto prosty przykład:

# shortcuts.ps1
# Szuka skrótów do nieistniejących plików w podanym katalogu

# Parametr: ścieżka do przeszukiwanego katalogu
param([string]$path = ".")

# Szukamy plików skrótów
$allLinks = [IO.Directory]::GetFiles($path, "*.lnk", [IO.SearchOption]::AllDirectories)

# Przeglądamy listę i szukamy nieistniejących
$wsh = New-Object -ComObject WScript.Shell
$invalidLinks = New-Object Collections.ArrayList
foreach ($linkFile in $allLinks)
{
    $lnk = $wsh.CreateShortcut($linkFile)
    if (-not ([IO.File]::Exists($lnk.TargetPath) -or [IO.Directory]::Exists($lnk.TargetPath)))
        { $invalidLinks.Add($linkFile) | Out-Null }
}
return [string[]]$invalidLinks.ToArray()

Dziwnym trafem najprostsze przykłady użycia COM są zwykle związane z tworzeniem skrótów (plików .lnk) przy pomocy metody CreateShortcut obiektu COM o identyfikatorze WScript.Shell. Obiekt ten był dawniej używany w skryptach hosta systemu Windows (Windows Scripting Host) i, jak widać, nadal zdarza mu się być użytecznym ;-)
Co jednak robi powyższy skrypt? Otóż nie tworzy żadnych nowych skrótów, a wręcz przeciwnie: wskazuje kandydatów do usunięcia, czyli skróty pokazujące na nieistniejące pliki lub katalogi. Na wyjściu dostajemy ich zgrabną listę, którą możemy przekierować na przykład do polecenia Remove-Item.
A tak możemy sobie wyczyścić Menu Start:

$startMenu = [Environment]::GetFolderPath([Environment+SpecialFolder]::StartMenu)
. ./shortcut.ps1 -path $startMenu | Remove-Item

I pomyśleć, że kiedyś napisałem do tego cały osobny program... :)

Tags: ,
Author: Xion, posted under Applications » 1 comment
 


© 2012 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.