Przez ostatni tydzień byłem na małych wakacjach, więc nie zajmowałem się niczym na tyle pożytecznym, by dało się o tym tutaj napisać ;] Ale na szczęście w międzyczasie zdarzyło się coś, o czym zdecydowanie warto wspomnieć.
Wczoraj mianowicie potwierdziły się spekulacje odnośnie kolejnego, trzeciego już dodatku do gry World of Warcraft. Rzecz była w sumie do przewidzenia, bowiem – podobnie jak dwa poprzednie – i to rozszerzenie zostało oficjalnie ogłoszone podczas BlizzConu. Co tam więc nowego ma zostać dodane do świata Azeroth?…
Ano odpowiedź brzmi: prawie nic albo niemal wszystko. W przeciwieństwie do poprzednich dodatków, ten nie doda żadnego nowego, dużego kontynentu do zwiedzania przez wysokopoziomowych graczy, a jedynie kilka nowych, mniejszych lokacji rozsianych po świecie. Tenże świat ma być jednak gruntownie przekształcony w wyniku tajemniczej i niezwykle brzemiennej w skutkach katastrofy – kataklizmu. Taki jest zresztą tytuł rozszerzenia – World of Warcraft: Cataclysm. Sprawcą całego zamieszania ma tu być Deathwing – pewien niezbyt przyjemny czarny smok, którego niektórzy może pamiętają z Warcrafta 2.
Jak widać, fabularnie prezentuje się to tak sobie, ale ciężko oczekiwać po tylu latach jakiejś wielkiej kreatywności :) To co bardziej interesujące, to oczywiście zmiany w samej grze, które dodatek ma przynieść. A są one następujące:
Część zmian wydaje się drobna, gdyż tak naprawdę są fragmentem dość długiej listy niewielkich, acz ciekawych modyfikacji, które dodatek ma przynieść; wybrałem te, które wydały mi się najważniejsze. Widać oczywiście, że są też i większe nowości, ale nietrudno zauważyć, że brak wśród nich jednej z bardziej oczekiwanych – nowej klasy. Jej w Kataklizmie nie uświadczymy.
Tak w dużym skrócie całość się przedstawia i trudno byłoby zaprzeczyć, że wygląda to całkiem ciekawie. Osobiście wydaje mi się, że to właśnie rozszerzenie może być decydujące dla dalszych losów gry. Jeśli okaże się, że lifting starych, liczących sobie już prawie 5 lat krain wypadł pomyślnie, może to przyciągnąć jeszcze większą liczbę graczy i zapewnić WoW-owi pozycję lidera na kolejne kilka lat – czyli mniej więcej na tyle, na ile rozciągają się prawdopodobne plany kolejnych 2-3 dodatków. (Tę liczbę można wywnioskować z tempa podnoszenia maksymalnego poziomu postaci i niedwuznacznych sugestii Blizzarda, że poziom 100 będzie ostatnim). Nie muszę pewnie dodawać, że szansa na to, iż będzie inaczej Cataclysm okaże się komercyjną klapą, są – znając Blizzarda – baaardzo małe :)
Jak będzie w rzeczywistości, zobaczymy pewnie gdzieś w okolicach końcówki przyszłego roku. Ja mam tylko nadzieję, że Starcraft 2 będzie wydany wcześniej ;D
Programy działające w tle (np. komunikatory) często używają różnego rodzaju “wyskakujących” powiadomień, które w założeniu mają pojawiać się tuż obok zasobnika systemowego (system tray), gdzie wspomniane programy mają swoje ikonki. W rzeczywistości pozycja tych komunikatów jest często hardcode‘owana w okolicach prawego dolnego rogu ekranu. Jeśli więc użytkownik przypadkiem ma pasek zadań gdzie indziej, to wtedy cały misterny plan idzie w… las ;]
Dlatego lepszym rozwiązaniem jest sprawdzanie, gdzie tak naprawdę ów pasek się znajduje. Przez długi czas można było to zrobić poprzez zwyczajne pobranie jego uchwytu (HWND
) i odczytanie wymiarów tak, jak każdego innego okna – np. przez GetWindowRect
:
Począwszy od Windows Vista nie jest to jednak możliwe, gdyż ze względu na bezpieczeństwo (tzw. izolacja sesji 0) nie da się już tak po prostu pobrać uchwytu do paska zadań ze zwykłej aplikacji. Istnieje na szczęście przenośny sposób na pobranie jego pozycji; w tym celu trzeba się posłużyć funkcją powłoki systemowej SHAppBarMessage
(z nagłówka shellapi.h i biblioteki shellapi.lib):
Tak możemy otrzymać nie tylko wymiary prostokąta okalającego pasek, ale też informację o tym, przy której krawędzi się on znajduje. Jest ona zawarta w polu uEdge
struktury zwracanej przez funkcję SHAppBarMessage
i równa którejś ze stałych ABE_BOTTOM
, ABE_TOP
, itd.
Mając te dwie informacje możemy nie tylko wyświetlać nasze powiadomienia w odpowiednim miejscu, ale też zapewnić, żeby w razie ich nagromadzenia “rosły” one w odpowiednim kierunku.
Kiedy w klasie pochodnej piszemy nową wersję funkcji wirtualnej, to w C++ powinniśmy zawsze uważać na to, żeby jej nagłówek był identyczny z tym, który zdeklarowano w klasie bazowej. To dlatego, że składnia języka nie wymaga w tej sytuacji żadnego słowa kluczowego w rodzaju override
. Jeśli więc coś nieopatrznie zmienimy, to nie uzyskamy nowej wersji funkcji, tylko zupełnie nową funkcję przeciążoną:
Taki efekt jest jest w gruncie rzeczy oczywisty i łatwy do zrozumienia. Pewnie więc będzie nieco zaskakujące dowiedzieć się, że tak naprawdę postać funkcji wirtualnej w klasie pochodnej może się trochę różnić, a mimo to wciąż będziemy mówili o tej samej funkcji.
Jak to jest możliwe?… Dopuszcza się mianowicie różnicę w typie zwracanym przez nową wersję funkcji wirtualnej. Może on być inny od typu zwracanego przez wersję z klasy bazowej – ale tylko pod warunkiem, że:
int
)const
/volatile
co typ pierwotnyPrzykładowo więc, jeśli w klasie bazowej funkcja wirtualna zwraca const A*
, to w klasie pochodnej może zwracać const B*
, o ile klasa B
dziedziczy publicznie po A
. Nie może za to zwracać samego B*
(niestałego) lub const X*
, gdy klasa X
jest niezwiązana z A
.
Do czego taki “myk” może się przydać? Najczęściej chodzi tutaj o sytuacje, gdy mamy do czynienia z równoległym dziedziczeniem kilku klas, które na każdym poziomie są związane ze sobą. Mogę sobie na przykład wyobrazić ogólny menedżer zasobów w grze, którego metoda wirtualna Get
zwraca wskaźnik na bazową klasę Resource
, a następnie bardziej wyspecjalizowany TextureManager
, który w tej sytuacji podaje nam wskaźnik na Texture
. (Oczywiście klasa Texture
w założeniu dziedziczy tu po Resource
). Czasami coś takiego może być potrzebne, aczkolwiek takie równoległe hierarchie dziedziczenia nie są specjalnie eleganckie ani odporne na błędy.
Lepszym przykładem jest wirtualny konstruktor kopiujący: metoda, która pozwala na wykonanie kopii obiektu bez dokładnej znajomości jego typu. Zwyczajowo nazywa się ją Clone
:
Dzięki temu że metoda jest wirtualna, można ją wywołać nie znając rzeczywistego typu obiektu (co nie jest możliwe w przypadku zwykłych konstruktorów, które w C++ wirtualne być nie mogą). W wyniku jej wywołania otrzymamy jednak tylko ogólny wskaźnik Object*
na powstałą kopię obiektu.
Gdybyśmy teraz nie zmienili typu zwracanego przez metodę w klasie pochodnej, to klonowanie podobne do tego:
wymagałoby dodatkowego rzutowania w górę, by mogło zostać przepuszczone przez kompilator.