传奇3代码完全解析
文章作者:66iyx.com 文章来源:传奇3G中文官方网站_传奇三私服_最新传奇3SF_66IYX黑金论坛 更新时间:2019/5/3 15:00:34 
 如果本文不是你想要的内容,你可以点击重新搜索相关内容:传奇3代码完全解析

传奇3代码完全解析(4)
unit Grobal2; //全局(服务器和客户端通用)消息,数据结构,函数等

interface
uses
Math,windows;
Const
BUFFERSIZE =1024; //缓冲定义

//客户端发送的命令
CM_SOFTCLOSE =0; //退出传奇(游戏程序,可能是游戏中大退,也可能时选人时退出)

 

//登录有关的命令 //CM: client msg,这些定义的消息都是客户端向服务端主动发送的
CM_IDPASSWORD =1; //客户端向服务器发送ID和密码
CM_QUERYUSERSTATE=2; //查询用户状态(用户登录进去,实际上是客户端向服务器索取查询最近一次
退出服务器前的状态的过程,服务器自动把用户最近一次下线以让游戏继续的一些信息返回到客户端)
CM_ADDNEWUSER =3; //新建用户,就是注册新账号,登录时选择了"新用户"并操作成功
CM_UPDATEUSER =4; //更新注册资料??
CM_SELECTSERVER =5; //选服务器,注意不是选区,盛大一区往往有(至多8个??group.dat中是这么写的)
//不止一个的服务器
CM_CHANGEPASSWORD =6; //修改密码
CM_NEWCHR =7; //创建新角色,注意不是上面所说的新建用户
CM_QUERYCHR =8; //登录成功,客户端显出左右角色的那一瞬
CM_DELCHR =9; //删除角色
CM_SELCHR =10; //选择角色开始游戏
CM_QUERYUSERNAME=11; //进入游戏,服务器返回角色名到客户端

CM_SAY =22; //角色发言

//客户端界面操作相关

CM_MAGICKEYCHANGE = 30; //魔法快捷键改变
CM_CREATEGROUP =42; //新建组队
CM_WANTMINIMAP =43; //用户点击了"小地图"按钮
CM_OPENGUILDDLG =45; //用户点击了"行会"按钮
CM_GROUPMODE=41; //关组还是开组
CM_ADDGROUPMEMBER =51; //组内添人
CM_DELGROUPMEMBER =52; //组内删人

CM_SPEEDHACKUSER =59; //用户加速作弊检测
CM_ADJUST_BONUS =60; //用户得到奖励??私服中比较明显,小号升级时会得出金钱声望等,不是很确定,
//求经过测试的高手的验证

CM_QUERYBAGITEMS=62; //查询包裹物品
CM_LOGINNOTICEOK = 63; //健康游戏忠告点了确实,进入游戏


//动作命令1
CM_TURN =4001; //转身(方向改变)
CM_WALK =4002; //走
CM_SITDOWN =4003; //挖
CM_RUN =4004; //跑
CM_HIT =4005; //普通物理近身攻击
CM_POWERHIT =4008; //攻杀
CM_LONGHIT =4009; //刺杀
CM_WIDEHIT =4010; //半月
CM_HEAVYHIT =4011; //烈火攻击
CM_BIGHIT =4012; //??
CM_THROW =4013; //抛符
CM_FIREHIT =4014; //??
CM_SPELL =4015; //施魔法

CM_CLICKNPC =3015; //用户点击了某个NPC进行交互

//动作命令2
CM_OPENDOOR =2001; //开门,人物走到地图的某个过门点时
CM_DROPITEM =2010; //从包裹里扔出物品到地图,此时人物如果在安全区可能会提示安全区不允许扔东西
CM_PICKUP =2011; //捡东西
CM_TAKEONITEM =2021; //装配装备到身上的装备位置
CM_TAKEOFFITEM = 2022; //从身上某个装备位置取下某个装备
CM_EAT =2023; //吃药
CM_BUTCH =2024; //54的野蛮?

//与商店NPC交易相关

CM_MERCHANTDLGSELECT =2030; //商品选择,大类
CM_MERCHANTQUERYSELLPRICE =2031; //返回价格,标准价格,我们知道商店用户卖入的有些东西掉持久或有特殊
//属性系统会自动加价
CM_MERCHANTQUERYREPAIRCOST =2032; //客户端向NPC取得修理费用

CM_USERSELLITEM =2040; //用户卖东西
CM_USERREPAIRITEM =2041; //用户修理东西
CM_USERSTORAGEITEM =2042; //用户寄存东西
CM_USERGETDETAILITEM =2043; //取得商品清单,比如点击"蛇眼戒指"大类,会出现一列蛇眼戒指供你选择
CM_USERBUYITEM =2044; //用户买入东西
CM_USERTAKEBACKSTORAGEITEM =2045; //用户向保管员取回东西
CM_USERMAKEDRUGITEM =2046; //用户制造毒药(其它物品)
CM_DROPGOLD =2050; //用户放下金钱到地上

CM_DEALTRY =2060; //开始交易,交易开始
CM_DEALCANCEL =2061; //取消交易
CM_DEALADDITEM =2062; //加东东到交易物品栏上
CM_DEALDELITEM =2063; //从交易物品栏上撤回东东???好像不允许哦
CM_DEALCHGGOLD =2064; //本来交易栏上金钱为0,,如有金钱交易,交易双方都会有这个消息
CM_DEALEND =2069; //交易成功,完成交易

CM_GUILDHOME =2070; //点击"行会主页"
CM_GUILDMEMBERLIST =2071; //点击"成员列表"
CM_GUILDADDMEMBER =2072; //增加成员
CM_GUILDDELMEMBER =2073; //踢人出行会
CM_GUILDUPDATENOTICE =2074; //修改行会公告
CM_GUILDUPDATERANKINFO =2075; //更新联盟信息(取消或建立联盟)

//装备项目
U_WEAPON =0; //武器
U_RIGHTHAND =1; //右手
U_DRESS =2; //衣服
U_HELMET =3; //头盔
U_NECKLACE =4; //项链
U_ARMRINGR =5; //右手戒指
U_ARMRINGL =6; //左手戒指
U_RINGR =7; //右戒指
U_RINGL =8; //左戒指

//服务器端发送的命令 sm:server msg,服务端向客户端发送的消息

//登录、新帐号、新角色、查询角色等
SM_NEWID_SUCCESS =101; //创建新账号成功
SM_NEWID_FAIL =102; //..失败
SM_PASSWD_FAIL =103; //验证失败,"服务器验证失败,需要重新登录"??
SM_NEEDUPDATE_ACCOUNT =104; //需要更新,注册后的ID会发生什么变化?
//私服中的普通ID经过充值??或者由普通ID变为会员ID,GM?
SM_UPDATEID_SUCCESS =105; //更新成功
SM_UPDATEID_FAIL =106; //更新失败
SM_PASSOK_SELECTSERVER = 107; //密码验证完成且密码正确,开始选服
SM_SELECTSERVER_OK =109; //选服成功
SM_QUERYCHR =111; //返回角色信息到客户端
SM_QUERYCHR_FAIL =112; //返回角色信息到客户端失败
SM_NEWCHR_SUCCESS =113; //新建角色成功
SM_NEWCHR_FAIL =114; //..失败
SM_CHGPASSWD_SUCCESS =115; //修改密码成功
SM_CHGPASSWD_FAIL =116; //..失败
SM_DELCHR_SUCCESS =117; //删除角色成功
SM_DELCHR_FAIL =118; //..失败
SM_STARTPLAY=119; //开始进入游戏世界(点了健康游戏忠告后进入游戏画面)
SM_STARTFAIL =120; //开始失败,玩传奇深有体会,有时选择角色,点健康游戏忠告后黑屏

SM_VERSION_FAIL =121; //客户端版本验证失败
SM_OUTOFCONNECTION =122; //超过最大连接数,强迫用户下线
SM_RECONNECT =125; //与服务器重连
SM_SENDNOTICE =202; //发送健康游戏忠告

SM_MYSTATUS =131; //我的状态,最近一次下线状态,如是否被毒,挂了就强制回城
SM_TIMECHECK_MSG = 227; //时钟检测,以免客户端作弊

SM_CHANGEMAP =201; //地图改变,进入新地图
SM_AREASTATE = 228; //周围状态
SM_NEWMAP =223; //新地图??
SM_MAPDESCRIPTION = 229; //地图描述,行会战地图?攻城区域?安全区域?
SM_LOGON =224; //logon

SM_CHANGELIGHT =240; //负重改变
SM_LAMPCHANGEDURA=241; //蜡烛持久改变
SM_LIGHTING =242; //免蜡开关

SM_OPENDOOR_OK=249; //通过过门点成功
SM_OPENDOOR_LOCK=250; //发现过门口是封锁的,以前盛大秘密通道去赤月的门要5分钟开一次
SM_CLOSEDOOR =251; //用户过门,门自行关闭

SM_DEATH=260; //正常死亡
SM_NOWDEATH =261; //秒杀?
SM_SKELETON=262; //尸体
SM_ALIVE =263; //复活??复活戒指
SM_ABILITY =264; //打开属性对话框,F11
SM_SUBABILITY=265; //打开输助属性对话框
SM_DAYCHANGING=266; //传奇界面右下角的太阳星星月亮
SM_WINEXP =267; //获得经验
SM_LEVELUP =268; //升级,左上角出现墨绿的升级字样
SM_HEALTHSPELLCHANGED =269; //治愈术使你的体力增加
SM_ADJUST_BONUS =280;

SM_STRUCK =310; //受攻击
SM_CHANGEFACE=311; //变脸,发型改变?
SM_OPENHEALTH=312; //
SM_CLOSEHEALTH =313; //
SM_INSTANCEHEALGUAGE=314; //实时治愈
SM_BREAKWEAPON=315; //武器破碎

//对话消息
SM_CRY=316; //喊话
SM_GROUPMESSAGE =347; //组内聊天!!
SM_GUILDMESSAGE =348; //行会聊天!~
SM_WHISPER=349; //私聊
SM_HEAR=351; //有人回你的话
SM_SYSMESSAGE=390; //系统消息,盛大一般红字,私服蓝字

SM_USERNAME =352; //
SM_CHANGENAMECOLOR=353; //名字颜色改变,白名,灰名,红名,黄名

传奇3代码完全解析(3)
Grobal2.pas,,,客户端和服务端通用的文件,定义了一些消息标识,数据结构,消息操作函数
Actor.pas,,,精灵(包括人物heros,npc,怪物mon)类,但是主要是heros类,NPC以及mon类派生于actor类
FState.dfm,,,各个子窗口,如f11,,f9等快捷键调出的窗口等
clmain.dfm,,cl-client,,客户端主界面窗体,打开可以看到它use了Fstate.dfm
fstate.pas,,FState.dfm的模块文件,,结合DWinCtl.pas和delphix中的绘图函数绘制各个子窗口在DDraw下真实效果的模块
IntroScn.pas 游戏的引导场景实现,比如登录选人等
clmain.pas,,clmain.dfm对应的源程序文件,,处理了程序启动时的网络连接,载入的wil客户端文件,等一系列操作
WIL.pas 比较重要的文件之一,,在原delphix图像包装类的基础上改装而成,封装了wil文件及其全部操作(结合wmUtil)
clEvent.pas 消息管理器
DWinCtl.pas 提供几个在DX下使用的控件
mir3.res 可能只有一个图标吧,就是那个龙字样的
SoundUtil.pas wav文件夹内声音文件的调用操作
wemade.pal 调色板文件
mir3.dpr 工程文件
EDCode.pas 消息加解密函数,6BIT加密算法的具体实现,用wpe截到的封包都是加密格式的,去掉密文前的!和最后的@
ClFunc.pas 输助函数库
MapUnit.pas 地图单元
AxeMon.pas 怪物单元,包含怪物在客户端的攻击等效果的实现,由actor类派生,不知为什么命名为axemon斧怪?
怪物对主角的伤害等算法当然不包含在,这是服务器res的工作
HerbActor.pas BOSS怪
PlayScn.pas 相对于引导场景,这里是游戏主场景画面实现
DrawScrn.pas 整个游戏场景的最终绘图工作
Soundfx.h 音乐文件操作的预编译头
magiceff.pas 客户端魔法效果的绘制与表现,当然也不包括魔法伤害实现.
hutil32.pas,cliUtil.pas 包含了一些输助函数等

mir3的地图场景在逻辑上分三层,从里到外分别为:1,tiles,,smtiles构成的地面背景,,2,,objects构成的地图前景,3,,,随时活动着的人物
(实际上人物的绘制不是地图单元要实现的,,,而是在场景单元playscn中实现的,,所以上面的3也可以删去)

tiles,,大块的地面,,smtiles,,小块地面,smtiles填补tiles不能补充的地面部分,,如一些大块tiles拼接处绘制不到的缝隙,如小块草地(实际
上这也是mir3丰富地图效果的手段之一,,而不仅仅是一种容错的技术),玩过mir3的地图编辑器都会知道,tiles大小一般为96*64,smtiles一般大
小为48*32,,即1tiles=4个smtiles,,前景图就是objects.wil-objects*.wil里面所包含的图片,主要是一些mir3游戏世界中的景观如建筑物等,
客户端的mir.set定义了一些objects的组合,这些组合解释了一些常见的mir3景观(npc屋等,,石柱等,,树木),,它是一个ascii码文件,请自行理
解它的各字段的意义,,它解释了景观用到的objects序列,,跨越多少mappoint

注意到有些objects里面的图片也绘制了地面,如沙巴克门前破碎的石柱佛面像等,此外,一个.map文件不仅定义了用哪些tiles,smtiles,objects
来绘制它,,还包括了其它一些必需的信息,如哪里不可走,,哪里是过门点,这些属性都是用地图编辑器生成一个地图文件后由地图编辑器自动
生成的,,mir3的引擎之地图读取单元就是读取.map文件为程序所用,,,实现的源程序片断如下:

{------------------------------------------------------------------------------}
// 地图常量信息定义
{------------------------------------------------------------------------------}
const

// 屏幕大小 整个游戏世界场景区的大小,,也即DDraw在屏幕上开辟的的最大绘图面积,mir3res使用了ddraw的800*600*8的绘图引擎
CLIENT_WIDTH = 800;
CLIENT_HEIGHT = 600;

// 显示地图的大小 600-445的屏幕部分用于显于道具栏
MAPSURFACE_WIDTH = 800;
MAPSURFACE_HEIGHT = 445;

// 地图单位/背景图片尺寸 //这就是"地图格",,如比奇地图大小为700*700,它的单位是mapunit,,一些NPC屋的地图大小就小多了,,
700*700折合像素单位为 (700*48)*(700*32),,想像一下要走遍比奇,在水平方向上要卷屏700*48/800次,,也即:刚好42屏
MAPUNIT_WIDTH = 48;
MAPUNIT_HEIGHT = 32;

// 地图单位中心象素点
MAPCENTER_X = (MAPSURFACE_WIDTH - MAPUNIT_WIDTH) div 2;
MAPCENTER_Y = (MAPSURFACE_HEIGHT - MAPUNIT_HEIGHT) div 2;

// 逻辑地图单位 (估计是微量屏幕移动的最小值)
LOGICALMAPUNIT = 20;

LONGHEIGHT_IMAGE = 35; // 地图上的任何一个前景图片最大的高度(以 MapPoint 为单位)

即任何一个包含在objects里面的图片高度都不能超过35,即35*32=1120,我们知道,,mappoint=48*32,,所以它超越了整个绘图区的高度600

{------------------------------------------------------------------------------}
// 地图文件结构定义
{------------------------------------------------------------------------------}
type
// 地图文件头结构 (52字节, 注意: 原文件头大小为56字节)
// 估计 UpdateDate 偏移有误
PMapHeader = ^TMapHeader;
TMapHeader = packed record
Width : Word; // 宽度 2
Height : Word; // 高度 2
Title : string[16]; // 标题 17
UpdateDate : TDateTime; // 更新日期 8
Reserved : array[0..22] of Char; // 保留 23
end;

// 地图点数据结构
PMapPoint = ^TMapPoint;
TMapPoint = packed record
BackImg : Word; // 背景图片索引(BackImg-1), 图片在 Tile.wil 中
MiddImg : Word; // 背景小图索引(MiddImg-1), 图片在 SmTile.wil 中
ForeImg : Word; // 前景
DoorIndex : Byte; // $80 (巩娄), 巩狼 侥喊 牢郸胶
DoorOffset : Byte; // 摧腮 巩狼 弊覆狼 惑措 困摹, $80 (凯覆/摧塞(扁夯))
AniFrame : Byte; // $80(Draw Alpha) + 橇贰烙 荐
AniTick : Byte;
Area : Byte; // 瘤开 沥焊
Light : Byte; // 0..1..4 堡盔 瓤苞
end;

"地图点"是一个重要的概念:我们把tiles,,smtiles构成的地面层称为背景,,把objects构成的地图层称为前景,,地图点的大小为mapunit定义
的像素大小,,,比奇有700*700个地图点数据,,现在来看一下它的结构,,前三个好理解,,doorindex,,dooroffset,,aniframe,,anitick,我们将
在下面的函数过程中求得它的实际意义,,,area为该地图点使用的是哪个objects文件(1.76版的mir3为0~6好像),,light,,天气文件,,lig0a~f

type

{------------------------------------------------------------------------------}
// TMirMap class
{------------------------------------------------------------------------------}
TMirMap = class(TObject)
private
FFileName: string;
FFileHandle: THandle; // WIN32 文件句柄 指向硬盘文件
FFileMapping: THandle; // 内存映射文件句柄 指向内存镜像文件,,也称内存"图像"文件,,这里的图像并非指图片
FFilePointer: Pointer; // 内存映射指针
FHeight: Word;
FWidth: Word;
FTitle: string;
FUpdateDate: TDateTime;

{ FCenterX: Integer;
FCenterY: Integer;
FShiftX: Integer; //我们分析过actor,,谈到shiftx,,,它就在这里发挥作用
FShiftY: Integer;}
FClientWidth: Integer;
FClientHeight: Integer;

procedure SetFileName(const value: string);
function GetPoint(X, Y: Word): PMapPoint;
protected

public
AniTick: Cardinal;
AniCount: Integer;

constructor Create(AClientWidth, AClientHeight: Integer);

destructor Destroy; override;

function CanMove(X, Y: Word): Boolean;

function CanFly(X, Y: Word): Boolean;

procedure BitBlt(DC: HDC; X, Y, AWidth, AHeight: Word);

procedure DrawBackground(Surface: IDirectDrawSurface7;
CenterX, CenterY, ShiftX, ShiftY: Integer);

procedure DrawForeground(Surface: IDirectDrawSurface7;
CenterX, CenterY, ShiftX, ShiftY: Integer; FirstStep: Boolean);

// 地图文件名, 指定为空串将关闭地图
property FileName: string read FFileName write SetFileName;

// 地图宽度
property Width: Word read FWidth; //注意delphi的这个机制
// 地图高度
property Height: Word read FHeight;
// 指定地图点的信息 (返回为 TMapPoint 指针, 直接指向地图文件)
property Point[X, Y: Word]: PMapPoint read GetPoint;

// 地图标题
property Title: string read FTitle;
// 地图更新日期(可能有误)
property UpdateDate: TDateTime read FUpdateDate;
end;


{ TMirMap }
implementation

procedure TMirMap.BitBlt(DC: HDC; X, Y, AWidth, AHeight: Word); //读取.map行列属性,根据一定规则,,进行实际的游戏世界中的贴图工作,,,图片资源当然在tiles,,,smtiles和objects中了:)
var
Pt: PMapPoint;
I, J: Word;
ImageIndex, AniIndex: Word;
AniCount: Integer;
// TODO: 本函数中的乘法运算可以优化为加法,CPU做加法比做乘法快
begin

// TODO: 更新正确的 AniCount
AniCount := 1000;

// 画背景图
for J := Y to Y + AHeight do
begin
// 如果纵坐标超出地图范围则终止
// TODO: 这时 I, J 定义为 Word, 会永远为 False, 应该更正, 包括下面的函数
if J >= FHeight then Break;

for I := X to X + AWidth do
begin
// 如果横坐标超出地图范围则终止
if I >= FWidth then Break;

// 取坐标处的地图信息
Pt := GetPoint(I, J);

// 如果是偶数行, 则画大块背景, 背景图尺寸是 96 * 64
if (J mod 2 = 0) and (I mod 2 = 0) then
begin
ImageIndex := Pt.BackImg and $7FFF;
if ImageIndex > 0 then
G_WilTile.BitBlt(ImageIndex - 1, DC, (I-X) * 48, (J-Y) * 32);
end;

// 画小图, 小图尺寸是 48 * 32 (小图用于填补一些大图画不到的边缘)
ImageIndex := Pt.MiddImg;
if ImageIndex > 0 then
G_WilTileSm.BitBlt(ImageIndex - 1, DC, (I-X) * 48, (J-Y) * 32);
end;
end;

// 画前景, 前景图尺寸是 48 * 32
for J := Y to Y + AHeight do
begin
// 如果纵坐标超出地图范围则终止
if J >= FHeight then Break;

for I := X to X + AWidth do
begin
// 如果横坐标超出地图范围则终止
if I >= FWidth then Break;

// 取坐标处的地图信息
Pt := GetPoint(I, J);

ImageIndex := Pt.ForeImg and $7FFF; //与计算,,hex的7fff=b
if ImageIndex > 0 then
begin
AniIndex := Pt.AniFrame;
if (AniIndex and $80 > 0) then AniIndex := AniIndex and $7F;
if AniIndex > 0 then
ImageIndex := ImageIndex + (AniCount mod (AniIndex * (Pt.AniTick + 1)))
div (Pt.AniTick + 1);
if (Pt.DoorOffset and $80 > 0) and (Pt.DoorIndex and $7F > 0) then
Inc(ImageIndex, Pt.DoorIndex and $7F);

// TODO: check value
if Pt.Area > 6 then
raise Exception.Create('err');

G_WilObjects[Pt.Area].BitBlt(ImageIndex - 1, DC, (I-X) * 48, (J-Y) * 32);
end;
end;
end;
end;

constructor TMirMap.Create(AClientWidth, AClientHeight: Integer); //creat是一个对象的默认初始工作过程
begin
FClientWidth := AClientWidth;
FClientHeight := AClientHeight;
end;

destructor TMirMap.Destroy; //destory是一个对象的默认销毁过程,把向系统申请过的资源还给系统
begin
// 关闭已打开的文件句柄等资源
FileName := '';

inherited;
end;

function TMirMap.GetPoint(X, Y: Word): PMapPoint;
begin
Result := IncPointer(FFilePointer, SizeOf(TMapHeader) + //指针按一定的步长前进,,以取得当前.map文件x,ymappoint坐标处的那个地图格信息(它是一个结构,,拥有前面定义的全部字段如for,,mid,,backimg,,aniframe)
SizeOf(TMapPoint) * (FHeight * X + Y));

// 注意, Mir 的地址存放似乎与一般地图方向不同
// Result := IncPointer(FFilePointer, SizeOf(TMapHeader) +
// SizeOf(TMapPoint) * (FWidth * Y + X));
end;

procedure TMirMap.SetFileName(const value: string); //动态加载.map文件,,进行前一个已经加载的.map文件与当前正要加载的.map文件之间在内存里的动态切换
begin
// 如果文件名相同则退出
if FFileName = value then Exit; //在这行执行时,,filename当然会有一个先前的值,,代表前一次加载的地图文件名,,在下面(// 保存地图文件名FFileName := value;)处

// 如果已经打开过地图文件, 则先释放先前的文件句柄
if FFileName <> '' then
begin
UnmapViewOfFile(FFilePointer); //取消文件从硬盘到内存的文件映射过程,,即CreateFileMapping的逆过程
CloseHandle(FFileMapping); //释放文件在内存的文件指针
CloseHandle(FFileHandle); //释放文件硬盘文件指针
end;

// 如果文件名为空则退出
if value = '' then Exit;

// 创建文件句柄
FFileHandle := CreateFile(PChar(value), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_RANDOM_ACCESS, 0);

if FFileHandle = INVALID_HANDLE_value then
raise Exception.CreateFmt('打开 "%s" 失败!', [value]);

// 创建文件映射
FFileMapping := CreateFileMapping(FFileHandle, nil, PAGE_READONLY, 0, 0, nil);

if FFileMapping = 0 then
begin
CloseHandle(FFileHandle);
raise Exception.CreateFmt('创建文件映射 "%s" 失败!', [value]);
end;

// 进行文件映射
FFilePointer := MapViewOfFile(FFileMapping, FILE_MAP_READ, 0, 0, 0);

if FFilePointer = nil then
begin
CloseHandle(FFileMapping);
CloseHandle(FFileHandle);
raise Exception.CreateFmt('映射文件 "%s" 失败!', [value]);
end;

// 读出地图头信息
FWidth := PMapHeader(FFilePointer)^.Width;
FHeight := PMapHeader(FFilePointer)^.Height;
FTitle := PMapHeader(FFilePointer)^.Title;
FUpdateDate := PMapHeader(FFilePointer)^.UpdateDate;

// 保存地图文件名
FFileName := value;
end;

//从setfilename()函数中我们可以提取到:一个.map文件映射入内存的3个步骤前二个步骤是建立文件指针,,第三个步骤也就是最终步骤是实际的映射过程,,,至此,,一个硬盘中的.map文件被映射入内存为程序暂时所用,,,直到下一次的setfilename()下一个.map文件映射入内存

所以释放.map文件的过程也应该先释放二个指针再来一次实际映射的逆过程并且这三个过程跟建立时的顺序是完全颠倒的.

当然在setfilename()中还有一些附加处理过程如保存地图文件名,,读取地图头信息为上下程序所用

procedure TMirMap.DrawBackground(Surface: IDirectDrawSurface7; //画背景即画tiles和smtiles...
CenterX, CenterY, ShiftX, ShiftY: Integer);
var
MapRect: TRect; // 需要绘制的 MAP 坐标范围
OffsetX, OffsetY: Integer; // X, Y 左上角偏移
AdjustX, AdjustY: Integer; // 在绘背景时是否需要调整最左/上行(由于BkImg以偶行/列方式绘制)
I, J: Integer;
Pt: PMapPoint;
ImageIndex: Word;
begin
// 自地图中间至最左/最上的宽度(象素)
I := (FClientWidth - MAPUNIT_WIDTH) div 2 - ShiftX;
J := (FClientHeight - MAPUNIT_HEIGHT) div 2 - ShiftY;

// 计算需要绘制的地图点范围(TMapPoint)
MapRect.Left := Max(0, CenterX - Ceil(I / MAPUNIT_WIDTH));
MapRect.Right := Min(FWidth, CenterX + Ceil((FClientWidth - I) / MAPUNIT_WIDTH));
MapRect.Top := Max(0, CenterY - Ceil(J / MAPUNIT_HEIGHT));
MapRect.Bottom := Min(FHeight, CenterY + Ceil((FClientHeight - J) / MAPUNIT_HEIGHT));

// MapRect.Left := MapRect.Left - MapRect.Left mod 2;
// MapRect.Top := MapRect.Top - MapRect.Top mod 2;

// 计算开始绘制时的偏移值(象素)
OffsetX := I - (CenterX - MapRect.Left) * MAPUNIT_WIDTH;
OffsetY := J - (CenterY - MapRect.Top) * MAPUNIT_HEIGHT;

// 绘制背景 (BkImg)
AdjustX := MapRect.Left mod 2;
AdjustY := MapRect.Top mod 2;
for I := MapRect.Left - AdjustX to MapRect.Right do
for J := MapRect.Top - AdjustY to MapRect.Bottom do
begin
if (I mod 2 = 0) and (J mod 2 = 0) then
begin
Pt := GetPoint(I, J);
ImageIndex := Pt.BackImg and $7FFF;
if ImageIndex > 0 then
begin
G_WilTile.Draw(ImageIndex - 1, Surface,
(I - MapRect.Left) * MAPUNIT_WIDTH + OffsetX,
(J - MapRect.Top) * MAPUNIT_HEIGHT + OffsetY,
FClientWidth, FClientHeight, False);
end;
end;
end;

// 绘制背景补充 (MidImg)
for I := MapRect.Left to MapRect.Right do
for J := MapRect.Top to MapRect.Bottom do
begin
Pt := GetPoint(I, J);
ImageIndex := Pt.MiddImg;
if ImageIndex > 0 then
begin
G_WilTileSm.Draw(ImageIndex - 1, Surface,
(I - MapRect.Left) * MAPUNIT_WIDTH + OffsetX,
(J - MapRect.Top) * MAPUNIT_HEIGHT + OffsetY,
FClientWidth, FClientHeight, False);
end;
end;
end;

procedure TMirMap.DrawForeground(Surface: IDirectDrawSurface7; CenterX, //画前景即objects
//这里要注意在客户端objects.wil文件中的图片的属性,打开它们,你可以发现它们有的为48*32从到48*448不等,,这就是为什么要在开头定义最高为35,,,448=32*14,,,
CenterY, ShiftX, ShiftY: Integer; FirstStep: Boolean);
var
MapRect: TRect; // 需要绘制的 MAP 坐标范围
OffsetX, OffsetY: Integer; // X, Y 左上角偏移
I, J: Integer;
Pt: PMapPoint;
InfoPtr: PImageInfo;
ImageIndex: Word;
AniIndex: Byte;
IsBlend: Boolean; //是否需要blend绘制,,,可见objects这些静态显示的图片(相对hum,,mon可以构成动画效果的系列图片),,,有时也需要blend显示,,想像一下,,比如盟重安全区圣诞树上的彩带之类的
begin
// 自地图中间至最左/最上的宽度(象素)
I := (FClientWidth - MAPUNIT_WIDTH) div 2 - ShiftX;
J := (FClientHeight - MAPUNIT_HEIGHT) div 2 - ShiftY;

// 计算需要绘制的地图点范围(TMapPoint)
MapRect.Left := Max(0, CenterX - Ceil(I / MAPUNIT_WIDTH));
MapRect.Right := Min(FWidth, CenterX + Ceil((FClientWidth - I) / MAPUNIT_WIDTH));
MapRect.Top := Max(0, CenterY - Ceil(J / MAPUNIT_HEIGHT));
MapRect.Bottom := Min(FHeight, CenterY + Ceil((FClientHeight - J) / MAPUNIT_HEIGHT) + LONGHEIGHT_IMAGE);

// 计算开始绘制时的偏移值(象素)
OffsetX := I - (CenterX - MapRect.Left) * MAPUNIT_WIDTH;
OffsetY := J - (CenterY - MapRect.Top) * MAPUNIT_HEIGHT;

// 绘制前景 (FrImg)
for I := MapRect.Left to MapRect.Right do
for J := MapRect.Top to MapRect.Bottom do
begin
Pt := GetPoint(I, J);
ImageIndex := Pt.ForeImg and $7FFF;
if ImageIndex > 0 then
begin
IsBlend := False;
AniIndex := Pt.AniFrame;
if AniIndex and $80 > 0 then
begin
IsBlend := True;
AniIndex := AniIndex and $7F;
end;
if AniIndex > 0 then
begin
Inc(ImageIndex, (AniCount mod (AniIndex * (Pt.AniTick + 1))) div (Pt.AniTick + 1));
end;
if (Pt.DoorOffset and $80 > 0) and (Pt.DoorIndex and $7F > 0) then
Inc(ImageIndex, Pt.DoorIndex and $7F);

// TODO: check value
if Pt.Area > 6 then
raise Exception.Create('err');

InfoPtr := G_WilObjects[Pt.Area].ImageInfo[ImageIndex - 1];

// 如果图片尺寸=48/32则按正常方式绘制
if FirstStep then
begin
if (InfoPtr^.Width = 48) and (InfoPtr^.Height = 32) then
begin
G_WilObjects[Pt.Area].Draw(ImageIndex - 1, Surface,
(I - MapRect.Left) * MAPUNIT_WIDTH + OffsetX,
(J - MapRect.Top) * MAPUNIT_HEIGHT + OffsetY,
FClientWidth, FClientHeight, True);
end
end
else begin
// 如果不是混合方式
if not IsBlend then
begin
if (InfoPtr^.Width <> 48) or (InfoPtr^.Height <> 32) then
G_WilObjects[Pt.Area].Draw(ImageIndex - 1, Surface,
(I - MapRect.Left) * MAPUNIT_WIDTH + OffsetX,
(J - MapRect.Top + 1) * MAPUNIT_HEIGHT + OffsetY - InfoPtr^.Height, // 要用减去图片高度
FClientWidth, FClientHeight, True);
end
else
// 否则, 是混合方式
{ G_WilObjects[Pt.Area].Draw(ImageIndex - 1, Surface,
(I - MapRect.Left) * MAPUNIT_WIDTH + OffsetX + InfoPtr^.PX - 2,
(J - MapRect.Top) * MAPUNIT_HEIGHT + OffsetY + InfoPtr^.PY - 68,
FClientWidth, FClientHeight, True)}
DrawBlend(Surface,
(I - MapRect.Left) * MAPUNIT_WIDTH + OffsetX + InfoPtr^.PX - 2,
(J - MapRect.Top) * MAPUNIT_HEIGHT + OffsetY + InfoPtr^.PY - 68,
FClientWidth, FClientHeight,
G_WilObjects[Pt.Area].Surfaces[ImageIndex - 1],
InfoPtr^.Width, InfoPtr^.Height, 0);
end;
end;
end;
end;

function TMirMap.CanFly(X, Y: Word): Boolean;
var
Pt: PMapPoint;
begin
Result := False;

if X >= FWidth then Exit;
if Y >= FHeight then Exit;

Pt := Point[X, Y];
Result := Pt.ForeImg and $8000 = 0;
if Result then
begin
if (Pt.DoorIndex and $80 > 0) and (Pt.DoorOffset and $80 = 0) then
Result := False;
end;
end;

function TMirMap.CanMove(X, Y: Word): Boolean;
var
Pt: PMapPoint;
begin
Result := False;

if X >= FWidth then Exit;
if Y >= FHeight then Exit;

Pt := Point[X, Y];
Result := (Pt.BackImg and $8000 = 0) and (Pt.ForeImg and $8000 = 0);
if Result then
begin
if (Pt.DoorIndex and $80 > 0) and (Pt.DoorOffset and $80 = 0) then
Result := False;
end;
end;

end.

本文[传奇3代码完全解析]由66iyx.com于2019/5/3 15:00:34录入本站!欢迎更多朋友投稿!谢谢大家支持本站的!
浏览次数:6