本帖最后由 zz010606 于 2025-6-8 19:27 编辑
WSE2联网扩展系列7-实操教程: 实现玩家登录功能的模组开发指南 在本期教程中,我们将深入讲解如何利用WSE2联网扩展为你的模组添加玩家账户功能。从基础配置到代码实现,手把手教你完成身份验证系统,确保玩家可以安全登录并保存个人数据。 账户系统的作用: 1 让玩家的进度、装备、金币等需要联网保存的数据不会随着存档丢失,可以长期保存,避免每次开新存档就重置。 2 个性化体验:让每个玩家拥有独特的游戏身份,支持扩展更多功能。 3 多人联机整合:结合WSE2的联网功能,实现玩家间的数据交互,如公会系统、排行榜或交易市场。【排行榜和交易市场我已经都代码实现,后续会逐步开源】 4 安全性与反作弊:通过追踪账户信息,可以防止通过单机的作弊功能作弊,确保联机环境的公平性。
无论你是想为模组添加完整的联网内容,还是仅仅需要一个基础的账户系统,这篇实操指南都将提供清晰的步骤与实用技巧,帮助你快速实现功能! 注:本位最终实现结果为:玩家可以通过账号密码登录,并且也可以登出。若登录成功,则云端会生成一份token并返回给骑砍。
【注】本文将有大量源码+实操教程。需要MS基础知识、web基础知识、数据库基础知识、登录鉴权token的基础知识。不适合纯小白。 一、前期准备: 2相关环境:python3环境、python3的flask库、flask_mysqldb库。【您若懂web,可以不需要用楼主的flask库,选择任何您觉得喜欢的库即可,如java的springboot库】 3准备云服务器一台。确保服务器的80端口可被访问到。
二、数据库设计 【如果不懂数据库,可以把我下面的内容问问 deepseek】 数据库中添加表"player",添加字段:"id" 、"account"、"password"、"token" "nikename"【这四个字段是必须要的,我的图片里还有其他字段,可以不添加】 id是唯一主键,用于确保每条玩家信息唯一. account是玩家的账号 password是玩家的密码 token是请求令牌,您不知道这有什么用没关系,但这个登录和拓展其他所有功能必须要的功能。
往表里添加一条记录 id:"001" nikename:"玩家1" account:"user01" password:"admin" token:"" 【token先空着】
三、 骑砍的 MS代码设计 module_game_menu.py添加一个网络菜单: 1您可以将这个菜单添加到任何菜单底下,我是添加到camp菜单内
- ("camp",mnf_scale_picture,
- "You set up camp. What do you want to do?",
- "none",
- [
- (assign, "$g_player_icon_state", pis_normal),
- (set_background_mesh, "mesh_pic_camp"),
- ],
- [
- ("camp_action_1",[(eq,"$cheat_mode",1)],"{!}Cheat: Walk around.",
- [(set_jump_mission,"mt_ai_training"),
- (call_script, "script_setup_random_scene"),
- (change_screen_mission),
- ]
- ),
- #添加这些内容-开始# # #
- ("camp_network_menu",[], "Network Content",
- [
- (jump_to_menu, "mnu_camp_network_menu"),
- ]
- ),
- #添加这些内容-结束
复制代码
结果展示:
2现在开始写这个菜单具体的代码
- #网络菜单开始-
- ("camp_network_menu",mnf_scale_picture,
- "Install WSE2 to use network functions",
- "none",
- [
- (set_background_mesh, "mesh_pic_camp"),#设置背景图片
- ],
- [
- ("network_login",[
- (eq,"$g_network_state",0),
- ],"Log in.",#登录
- [
- (start_presentation, "prsnt_network_login"),
- ]
- ),
- ("network_logout",[
- (eq,"$g_network_state",1),
-
- ],"Log out.",#登出
- [
- (send_post_message_to_url_advanced,"str_network_doLogout_url","@version=1.0&token=12345",s0,"script_network_logout", "script_network_fail", 0),#这里的"@version=1.0&token=12345"是随便写的,可以为任意值
- (jump_to_menu,"mnu_camp"),
- ]
- ),
-
- ("network_back",[],"back.",
- [
- (change_screen_return),
- ]
- ),
-
- ]
- ),
- #网络菜单结束-
复制代码 结果展示:
3现在开始写具体的登录界面,请在module_presentation.py添加下面代码
- #网络登录界面
- ("network_login",0,mesh_load_window,[
- (ti_on_presentation_load,
- [(set_fixed_point_multiplier, 1000),
-
- (str_store_string, s1, "@network login"),
- (create_text_overlay, reg1, s1, tf_center_justify),
- (position_set_x, pos1, 300),
- (position_set_y, pos1, 500),
- (overlay_set_position, reg1, pos1),
- (overlay_set_text, reg1, s1),
- (str_store_string, s1, "@network password"),
- (create_text_overlay, reg2, s1, tf_center_justify),
- (position_set_x, pos1, 300),
- (position_set_y, pos1, 400),
- (overlay_set_position, reg2, pos1),
- (overlay_set_text, reg2, s1),
- (create_simple_text_box_overlay, "$g_presentation_obj_name_login"),
- (create_simple_text_box_overlay, "$g_presentation_obj_name_pwd"),
- (position_set_x, pos1, 500),
- (position_set_y, pos1, 500),
- (position_set_x, pos2, 500),
- (position_set_y, pos2, 400),
- (overlay_set_position, "$g_presentation_obj_name_login", pos1),
- (overlay_set_position, "$g_presentation_obj_name_pwd", pos2),
-
- (create_button_overlay, "$g_presentation_obj_name_network_commit", "@Continue...", tf_center_justify),
- (position_set_x, pos1, 350),
- (position_set_y, pos1, 200),
- (overlay_set_position, "$g_presentation_obj_name_network_commit", pos1),
- (create_button_overlay, "$g_presentation_obj_name_network_cancel", "@Cancel", tf_center_justify),
- (position_set_x, pos1, 550),
- (position_set_y, pos1, 200),
- (overlay_set_position, "$g_presentation_obj_name_network_cancel", pos1),
- (presentation_set_duration, 999999),
- ]),
- (ti_on_presentation_event_state_change,
- [(store_trigger_param_1, ":object"),
- (try_begin),
- (eq, ":object", "$g_presentation_obj_name_login"),
- (str_store_string, s7, s0),#s7存账号
- (else_try),
- (eq, ":object", "$g_presentation_obj_name_pwd"),
- (str_store_string, s8, s0),#s8存密码
- (else_try),
- (eq, ":object", "$g_presentation_obj_name_network_cancel"),
- (presentation_set_duration, 0),
- (else_try),
- (eq, ":object", "$g_presentation_obj_name_network_commit"),
- (str_store_string,s0,"str_network_account_and_pwd"),
- (send_post_message_to_url_advanced,"str_network_doLogin_url","@user_agent_string",s0,"script_network_login", "script_network_fail", 0),
- (presentation_set_duration, 0),
-
- (try_end),
- ]),
- ]),
- #网络登录结束
复制代码 结果:
4补充全str_network_doLogin_url与str_network_account_and_pwd
请打开module_string.py 添加下面代码: 请注意,请把"http://www.xxxxx.com/doLogin"这部分换成你自己的云服的公网ip如 "http://127.0.0.1/doLogin"
- ("network_show_info","Network Infomation"),
- ("network_doLogin_url","http://www.xxxx.com/doLogin"),
- ("network_doLogout_url","http://www.xxxx.com/doLogout"),
- ("network_account_and_pwd","account={s7}&password={s8}"),
复制代码
5补充上面所有提到的script
请打开module_script.py 添加下面代码:
- #接口失败回调
- ("network_fail", [
- (dialog_box,"@网 络 错 误 ", "str_network_show_info"),
- (jump_to_menu,"mnu_camp"),
- ]),
- #接口成功回调
- ("network_login", [#
- # (store_script_param, ":num_integers", 1),
- # (store_script_param, ":num_strings", 2),
- (try_begin),
- (eq,reg0,1),
- (assign,"$g_network_state",1),
- (troop_set_plural_name, "trp_token", s0),#在骑砍存储token值,之后所有的请求就都会带着这个值发给服务器了
- (dialog_box,"@登 陆 成 功 ", "str_network_show_info"),#如果接受回来的数据是1就表示成功
- (jump_to_menu,"mnu_camp"),
- (display_message, s0, 0x66ccff),
- (else_try),
- (eq,reg0,2),
- (dialog_box,"@账 号 密 码 错 误 ", "str_network_show_info"),#2表示账号密码错误
- (else_try),
- (eq,reg0,3),#3表示状态有问题
- (dialog_box,"@用 户 状 态 不 可 用 ", "str_network_show_info"),
- (try_end),
- ]),
- #登出
- ("network_logout", [
- # (store_script_param, ":num_integers", 1),
- # (store_script_param, ":num_strings", 2),
- (assign,"$g_network_state",0),
- (dialog_box,"@注 销 成 功 ", "str_network_show_info"),
- ]),
复制代码
6请打开module_troop.py 添加下面代码:
- #token
- ["token","token","token",tf_female|tf_guarantee_boots|tf_guarantee_armor,0,0,fac_commoners,[],def_attrib|level(2),wp(40),knows_common,woman_face_1,woman_face_2],
- #版本号
- ["version","version","109",tf_female|tf_guarantee_boots|tf_guarantee_armor,0,0,fac_commoners,[],def_attrib|level(2),wp(40),knows_common,woman_face_1,woman_face_2],
复制代码
四、服务器开发
楼主是采用flask项目架构,分为很多工具py文件和业务文件,解耦程度高。不方便展示。所以楼主把核心内容的“登录”,“登出”功能提炼到一份文件出来:
服务器需要配置python3环境,安装好flask库与flask_mysqldb 库。然后运行本文件即可。
服务器开发部分核心就是 登录功能接收到账号密码后进行检验是否对应合理,然后针对结果返回'1"或者“2” ”3“ 。登出核心功能就是根据传输过来的token清楚云端的对应的token。
【但是楼主给的骑砍MS 代码里logout接口其实是废的,没有把token传输过来,这是因为楼主用于实现登出主要是利用MS里$g_network_state这个全局变量,若登出就变为0,这样玩家就进入不到网络接口的菜单里了,就得强制登录了哈哈】
- from datetime import datetime
- import uuid
- from flask import Flask, request
- from flask_mysqldb import MySQL
- from urllib.parse import parse_qs
- app = Flask(__name__)
- # MySQL配置
- app.config['MYSQL_HOST'] = 'localhost' # 根据实际情况修改
- app.config['MYSQL_USER'] = 'root' # 根据实际情况修改
- app.config['MYSQL_PASSWORD'] = ' ' # 根据实际情况修改
- app.config['MYSQL_DB'] = 'your_db' # 根据实际情况修改
- mysql = MySQL(app)
- # 工具函数 - 获取成功状态码
- def getSuccessStateCode():
- return "1"
- # 工具函数 - 检查UserAgent
- def checkUserAgent(userAgent):
- cursor = mysql.connection.cursor()
- userAgent = parse_qs(userAgent)
- version = userAgent.get('version', [''])[0] # 获取version
- token = userAgent.get('token', [''])[0] # 获取token
- # 验证Token有效性
- cursor.execute("SELECT id FROM player WHERE token = %s", (token,))
- user = cursor.fetchone()
- if not user:
- return '4', token # Token无效
- # 检查版本是否匹配
- cursor.execute("SELECT version FROM mod_update_logs ORDER BY update_date DESC LIMIT 1")
- latest_version = cursor.fetchone()
- if latest_version:
- latest_version = latest_version[0]
- if version != latest_version:
- cursor.close()
- return '6', token # 版本不匹配
- return "ok", token
- # 工具函数 - 检查UserAgent状态
- def checkUserAgentStatus(status):
- if status == "4":
- return status
- return None
- # 登录接口
- @app.route('/doLogin', methods=['POST'])
- def doLogin():
- cursor = mysql.connection.cursor()
-
- # 获取登录数据
- data = request.form
- account = data.get('account')
- pwd = data.get('password')
- # 查询用户信息
- cursor.execute("SELECT account, password, state FROM player WHERE account = %s", (account,))
- user = cursor.fetchone()
- # 验证用户
- if not user: # 用户不存在
- return '2'
- if user[1] != pwd: # 密码错误
- return '2'
- if user[2] != 1: # 账号状态异常
- return '3'
- # 生成唯一Token
- while True:
- token = str(uuid.uuid4())
- cursor.execute("SELECT * FROM player WHERE token = %s", (token,))
- if not cursor.fetchone():
- break
- # 更新用户Token和登录时间
- cursor.execute(
- "UPDATE player SET token = %s, last_login_time = %s WHERE account = %s",
- (token, datetime.now(), account)
- )
- cursor.connection.commit()
- cursor.close()
-
- # 返回成功响应和Token
- return f"{getSuccessStateCode()}|{token}"
- # 登出接口
- @app.route('/doLogout', methods=['POST'])
- def doLogout():
- cursor = mysql.connection.cursor()
- # 检查UserAgent
- userAgent = request.headers.get('User-Agent')
- userAgentStatus, token = checkUserAgent(userAgent)
- if checkUserAgentStatus(userAgentStatus):
- return checkUserAgentStatus(userAgentStatus)
- try:
- # 清空用户Token
- cursor.execute("UPDATE player SET token = NULL WHERE token = %s", (token,))
- mysql.connection.commit()
- return '1' # 登出成功
- except Exception as e:
- print(f"登出错误: {e}")
- return '0' # 登出失败
- finally:
- cursor.close()
- if __name__ == '__main__':
- app.run(host='0.0.0.0',debug=True)
复制代码
实践我帖子时候遇到问题,帖子底下留言。或者加我企鹅号1730239726
写帖不易,路过看帖子欢迎您写个留言或者给个花,激励楼主继续更新
|